1 关于Spring整合Redis分片

1.1 关于redis分片的说明

redis分片的主要的作用是:实现内存数据的扩容。
redis分片如果宕机了,就不能实现高可用了。
redis的分片的计算发生在业务服务器(比如tomcat服务器)中。(tomcat先计算,再存到redis中)
redis分片的执行的效率是最高的。

1.2 Spring整合redis分片

1.2.1 编辑redis.properties文件

springboot 哨兵配置 密码 哨兵机制 java_java

每个node的ip与port用:连接

node与node之间用,连接

1.2.2 编辑Redis配置类 JedisConfig.java

需要将之前 单个redis的配置注释掉。
将redis分片对象ShardedJedis 交给Spring去管理

@Configuration  //标识  这是一个配置类
@PropertySource("classpath:/properties/redis.Properties")
public class JedisConfig {

    @Value("${redis.nodes}")
    private String nodes;   //node1,node2,node3

    /**
     * 将ShardedJedis交给spring管理
     */
    @Bean
    public ShardedJedis shardedJedis(){

        //第2步:获取每个节点的信息
        //从redis.properties中读取到的redis.nodes的信息为:
        //192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
        //2.1 将nodes用在,处分隔,形成这样的字符串数组["192.168.126.129:6379","192.168.126.129:6380","192.168.126.129:6381"]
        String[] strNodes = nodes.split(",");  //[node1,node2,node3]

        //第3步 准备一个集合shards,用来存放JedisShardInfo,即每个redis单体的信息(host和port)
        List<JedisShardInfo> shards = new ArrayList<>();

        //第4步 遍历strNodes这个字符串数组,把结果放进List集合中
        for (String  node  :  strNodes) {
            //得到的每一个元素:["192.168.126.129:6379"] 和 ["192.168.126.129:6380"] 和 ["192.168.126.129:6381"]
            //将每一个元素在 :处分隔,脚标为[0]的地方就是字符串形式的host:"192.168.126.129",
            //                       脚标为[1]的地方就是字符串形式的port:  "6379" 等
            String host = node.split(":")[0];
            String port = node.split(":")[1];
            //由于新建JedisShardInfo时,人家要求host可以是string类型,但port得是int类型。
            //所以通过Integer.parseInt()的方法将string转化为int
            int port1 = Integer.parseInt(port);
            
            //第5步 创建一个redis分片信息对象,每个这样的对象里都封装着1个redis单体的信息(host和port)
            JedisShardInfo jedisShardInfo = new JedisShardInfo(host,port1);
            
            //第6步 将每个redis分片信息对象存入shards集合中
            shards.add(jedisShardInfo);
        }

        //第1步 由于分片后的redis对象是ShardedJedis,所以得新建个ShardedJedis对象。
        //新建这个对象时需要shards参数,shards就是包含各个redis单体的信息的集合,所以得想办法得到redis单体的信息(host和port)
        ShardedJedis shardedJedis = new ShardedJedis(shards);

        //第7步
        return shardedJedis;
    }
}

1.2.3 切换CacheAop切面中的redis对象

原来的CacheAop切面中的redis缓存对象是 单体的redis对象。

这次配置了redis分片,就替换成redis分片对象ShardedJedis吧。

springboot 哨兵配置 密码 哨兵机制 java_java_02

1.3 Redis高可用机制的实现

与数据库的高可用机制类似,Redis作为缓存数据库也有它自己的高可用机制。
如果主机gg了,就要在从机中选拔出一个去当主机。

1.3.1 Redis哨兵(sentinel)机制说明

问题分析:
之前的redis分片中(假如有3台redis数据库),如果有1台redis(eg:6379)宕机了,那它里面的数据就没了。用户再从它里查询数据时就查不到了。

要想实现redis数据库的高可用(即1台redis挂到了,它里的缓存数据会转移到其他的redis服务器,保证用户能正常访问到他的数据),还要先设置redis的主从复制功能。

1.3.2 Redis主从机制说明

redis的主从复制与数据库的主从复制功能类似,也是设置redis主机,与redis从机,主机定时将数据与从机同步。
当主机宕机后,从机上任,充当主机。

1.3.3 Redis高可用的实现过程----复制哨兵目录

提前准备:先关闭Redis服务器。

1.复制shards文件到sentinel文件夹中

由于在之前分片的阶段,新建了一个shards文件,里面放的是6379.conf,6380.conf,6381.conf这3个redis数据库的配置文件。

所以,在实现Redis高可用的过程中,也要用到这3个文件。我就直接把这3个配置文件CV过来用就行了。

执行复制命令后,会在redis目录下,自动生成一个sentinel文件夹,里面放着shards文件夹中的3个conf文件和1个dump.db的持久化文件。

springboot 哨兵配置 密码 哨兵机制 java_java_03

2.删除持久化文件dump.rdb

使sentinel文件夹中只有3个redis数据库的配置文件。

springboot 哨兵配置 密码 哨兵机制 java_springboot 哨兵配置 密码_04

3.同时开启3台redis服务器

springboot 哨兵配置 密码 哨兵机制 java_springboot 哨兵配置 密码_05

springboot 哨兵配置 密码 哨兵机制 java_java_06


由此可以看出,6379,6380,6381三台redis服务器都已经开始运行了。

1.3.4 实现主从挂载

搭建规则:
6379当做主机 6380/6381 当做从机

1.3.4.1 查看当前redis服务器是主机还是从机

进入当前的redis数据库(eg:6379)

根据语句:info replication

springboot 哨兵配置 密码 哨兵机制 java_java_07

然鹅~~~~在没设置主从机的关系时,进入每一个redis数据,一查主从状态,都是主机状态!!

谁都不服谁,谁都想当主机。

于是,我只能人为地去指定从机。

1.3.4.2 设置从机

语句:slaveof 主机IP 主机端口 进入6380库中,执行下面语句

springboot 哨兵配置 密码 哨兵机制 java_redis_08


3.实现主从的挂载 slave of 主机IP 主机端口

进入6380中,想让它当从机。让它去连接6379主机。

命令:SLAVEOF 192.168.126.129 6379

springboot 哨兵配置 密码 哨兵机制 java_数据库_09


再进入6381中,执行相同的操作。

1.3.4.3 查看主机和从机的状态

1.进入到主机6379中,执行:

springboot 哨兵配置 密码 哨兵机制 java_配置文件_10

2.进入到从机6380和6381中,执行

springboot 哨兵配置 密码 哨兵机制 java_配置文件_11

1.3.4.4 一不小心主从关系挂载错了,怎么办

比如,不小心将6380设置成了主机,6379设置成了从机===

方法:重启redis数据库(比如:6380和6379 ,注意:这两个都要重启)后,6380和6379的挂载关系就恢复了默认状态,就都成了主机,并且没有从机。重新设置就好了。

springboot 哨兵配置 密码 哨兵机制 java_redis_12

解释:

由于slaveof指令是在内存中生效的. (注意:SLAVEOF是连着写的)

如果内存资源释放,则主从的关系将失效.

为了实现永久有效,应该将主从的关系写在配置文件中即可.

新问题 :如果主机意外宕机,则由谁来完成配置文件的修改呢,即在从机中选出一个作为主机?

那就是“哨兵”。

1.4 redis哨兵的工作原理

哨兵的作用:
监控当前redis主机的状态,并且哨兵通过读取redis主机的配置文件,还能知道主机下的从机都有谁。
如果当前主机gg了,哨兵们就会通过选举,在当前的从机中选出一个升任为主机(修改从机的配置文件)。
过一会儿,如果原主机又活过来了,哨兵就会让原主机降职为从机(修改原主机的配置文件)

工作原理:

  1. 当哨兵启动时,会监控当前的主机的信息,同时获取连接在当前主机的从机们的信息。
  2. 当哨兵利用心跳检测机制(PING-PONG),检验当前主机是否正常.
    如果连续3次发现主机没有响应回信息.则哨兵们开始进行选举.
  3. 当哨兵选举完成之后.其他的节点都会当做新主机的从机.

1.5 哨兵机制的实现

1.5.1 复制哨兵配置文件sentinel.conf

在redis的跟目录下,原本就有一个哨兵的配置文件sentinel.conf,但我不能随意改动它,这个文件就这一份,万一改错了,找都找不回来。

我新建个文件夹,叫sentinel,把sentinel.conf复制到sentinel中,通过修改这个配置文件复制品,来设置哨兵。

springboot 哨兵配置 密码 哨兵机制 java_springboot 哨兵配置 密码_13

1.5.2 修改这个配置文件

  1. 关闭保护模式(默认情况下,保护模式是开启着的)
  2. springboot 哨兵配置 密码 哨兵机制 java_数据库_14

  3. 哨兵的默认端口号:26379
  4. 开启后台运行功能
  5. springboot 哨兵配置 密码 哨兵机制 java_配置文件_15

  6. 修改哨兵对主机的监控 monitor:监控 , mymaster:当前主机 , 最后面的数字代表从机获得几票就能升任为主机
  7. springboot 哨兵配置 密码 哨兵机制 java_数据库_16

  8. 主机gg多长时间后,哨兵们进行选举
    比如设置主机宕机10s后,哨兵们就开始选举新的主机
  9. springboot 哨兵配置 密码 哨兵机制 java_redis_17

  10. 修改哨兵选举的超时时间
    哨兵选举超过多长时间 还没选举出主机,那么就不选举了。脑裂吧。
  11. springboot 哨兵配置 密码 哨兵机制 java_redis_18

这个涉及到一个概念:脑裂现象
如果有偶数个哨兵(比如2个),它俩在2个从机中进行选举。每次投票两个哨兵选的都不是同一个从机,那意见就永远都不能统一,就永远都选不出主机来,即投票失败。
一般规定,如果连续3次投票失败,就可能发生脑裂现象。
问:脑裂现象发生的最高的概率为多大(就是上面那个,2个哨兵,在2个从机中进行选举,投票3次):
(第1次投票:AA AB BA BB ,平票的情况:AB和BA 概率为1/2)
(第2次投票:AA AB BA BB ,平票的情况:AB和BA 概率为1/2)
(第3次投票:AA AB BA BB ,平票的情况:AB和BA 概率为1/2)
那么3次下来,平票的概率为(1/2)^3 即 1/8
解决策略:增加选举的次数,可以降低脑裂现象的发生。(哨兵数量最好为奇数个)

1.5.3 哨兵高可用测试

测试策略:当redis主机(6379)宕机后,10s后是否有从机被选为了主机;然后再开启原主机(6379),看它是否变成了从机。

  1. 启动哨兵
[root@localhost sentinel]# redis-sentinel sentinel.conf
  1. 关闭redis主机(6379),之后等待10s(因为上一步设置的是主机gg10s后开始选举新主机),哨兵们开始选举出一个新的主机。然后我再把原主机(6379)拯救回来,看看它现在是不是降职为从机了。

1.6 在程序中进行哨兵机制的测试

在TestRedis.java中,测试哨兵

/**
     * 哨兵sentinel的测试
     */
    @Test
    public void testSentinel(){

        //第2步 新建个set集合,用于存放哨兵们
        Set<String> sets = new HashSet<>();
        //第3步 把本例中设置的哨兵放进去
        sets.add("192.168.126.129:26379");

        //第1步 新建个哨兵池对象,注意是 哨兵池,里面可能有多个哨兵。本例中先放1个哨兵用着
        //第一个参数是主机的名字,这个是在sentinelPool中第84行定义的
        //第二个参数人家规定要一个set集合,里面放的是一个一个的哨兵们(本例中只有一个哨兵)
        JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster",sets);

        //第4步 通过哨兵池sentinelPool去.getResource() 就可以得到当前的redis主机对象jedis
        Jedis jedis = sentinelPool.getResource();
        //第5步 往jedis中存一个数据进行测试
        jedis.set("aaa","测试哨兵");
        //第6步 真的能取出来吗?
        System.out.println(jedis.get("aaa"));
        //把这个连接还给sentinelPool池
        jedis.close();

    }

1.7 关于 redis分片和redis哨兵的优缺点的总结

分片机制:
优点:
1. 主要的作用是 实现redis内存的扩容。
2. 由于运算(一致性hash)发生在业务服务器(比如tomcat),所以执行的效率更高。
缺点:
1. Redis的分片没有高可用的效果,如果其中一个节点gg了,则当用户通过这个节点查数据时,程序就会报错。

哨兵机制:
优点:
1.实现了Redis的高可用,哨兵可以灵活地监控各个redis数据库的状态。当redis的主机发生了宕机,哨兵可以自动进行选举,选出新主机,实现redis数据的迁移。
缺点:
1.哨兵所监控的各个redis节点中的数据都是相同的,如果存的都是相同的海量数据,就会浪费大量的内存空间。不划算。
2.哨兵虽然可以实现redis的高可用,但如果哨兵本身gg了,没有人替他干活了,程序依然运行不下去。不好不好。

所以,如果想要在最大程度上减少损耗,那么不建议引入第三方监控(比如哨兵)。