暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis Sentinel哨兵模式

并发编程之美 2020-09-09
1411

介绍

Redis的主从复制有两个作用,第一个是给主提供了备份,当主节点挂掉之后,备份中有完整的数据,第二个是对主进行一个分流,例如读写分离功能,将大部分的读分流到从节点中去执行,减轻主节点的压力。但是主从复制存在的问题是当主节点出现问题时,需要手动故障转移,将一个从节点晋升为主节点;并且写能力和存储能力受到限制,都限制在一个节点上,其他节点都是备份。例如之前介绍的主从复制故障处理:

上述过程也可以依赖于脚本来完成,使用脚本不断与监控master节点是否有问题,如果有问题则将master进行下线,然后选择一个新的master,让其他节点都slaveof新的master节点。Redis针对上述问题提供了Redis sentinel的高可用实现,可以完成故障发现、故障自动转移、配置中心以及客户端通知等,下面我们看下Redis sentinel是如何解决这个的。


一、Redis Sentinel架构

Redis Sentinel架构首先依然是一个主从结构,还有多个sentinel节点,你可以把它想象成一个Redis进程,但这个进程它不会存储数据,它的作用是完后对Redis的一个故障判断与故障转移的处理,它可以对master和slave进行一个监控,每套master和slave会使用master-name作为一个标识。Redis Sentinel中的Sentinel节点个数应该大于等于3且最好为奇数。

对于客户端来说,它不会直接从Redis获取信息,不会记录某个Redis的ip地址,而是直接记录Redis sentinel的地址。客户端不关心谁是master和slave,而是由sentinel来监控master和slave的状态。


二、Redis Sentinel安装与配置

本机安装部署sentinel说明:

sentinel的主要配置说明:

    port ${port}
    daemonize yes
    pidfile "/var/run/redis-sentinel.pid"
    logfile "${port}.log"
    dir "/home/duan/myhome/soft/redis/redis-5.0.3/data"
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 30000
    sentinel parallel-syncs mymaster 1
    sentinel failover-timeout mymaster 180000
    • 2表示至少有多少个sentinel认为master有问题了则进行故障转移

    • 30000毫秒表示30秒sentinel ping master ping不通,则认为master有问题

    • 下面两个是复制配置,我们选择了新的master之后,其他老的slave会对新的master进行复制,1表示每次只能复制一个,减轻master的压力

    • 最后一个是故障转移时间


    下面介绍下Redis Sentinel的配置演示。

    6379Redis配置:

      [root@common redis-5.0.3]# cat redis_6379.conf 
      bind 0.0.0.0
      port 6379
      daemonize yes
      pidfile var/run/redis_6379.pid
      logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/6379.log"
      dir /home/duan/myhome/soft/redis/redis-5.0.3/data/

      6380Redis配置:

        [root@node1 redis-5.0.3]# cat redis_6380.conf 
        port 6380
        daemonize yes
        pidfile var/run/redis_6380.pid
        logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/6380.log"
        dir home/duan/myhome/soft/redis/redis-5.0.3/data/
        slaveof 192.168.1.102 6379

        6381Redis配置:

          [root@node2 redis-5.0.3]# cat redis_6381.conf 
          port 6381
          daemonize yes
          pidfile var/run/redis_6381.pid
          logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/6381.log"
          dir home/duan/myhome/soft/redis/redis-5.0.3/data/
          slaveof 192.168.1.102 6379

          可以通过快速生成从节点的配置信息:

            [root@common config]# sed 's/6379/6380/g' redis_6379.conf > redis_6380.conf
            [root@common config]# sed 's/6379/6381/g' redis_6379.conf > redis_6381.conf 

            启动redis:

              [root@common redis-5.0.3]# redis-server redis_6379.conf 
              [root@common redis-5.0.3]# ps -ef | grep redis
              root 4453 1 0 23:24 ? 00:00:00 redis-server *:6379
              root 4460 2983 0 23:24 pts/0 00:00:00 grep --color=auto redis
              [root@common redis-5.0.3]# redis-server redis_6380.conf
              [root@common redis-5.0.3]# redis-server redis_6381.conf

              启动客户端:

                [root@common redis-5.0.3]# redis-cli 
                127.0.0.1:6379> ping
                PONG


                [root@node1 redis-5.0.3]# redis-cli -p 6380
                127.0.0.1:6380> ping
                PONG


                [root@node2 redis-5.0.3]# redis-cli -p 6381
                127.0.0.1:6381> ping
                PONG

                查看主节点信息:

                  127.0.0.1:6379> info replication
                  # Replication
                  role:master
                  connected_slaves:2
                  slave0:ip=192.168.1.104,port=6380,state=online,offset=154,lag=2
                  slave1:ip=192.168.1.106,port=6381,state=online,offset=154,lag=1
                  master_replid:28cd61c74cf41734081951ba93c6a89055aade20
                  master_replid2:0000000000000000000000000000000000000000
                  master_repl_offset:154
                  second_repl_offset:-1
                  repl_backlog_active:1
                  repl_backlog_size:1048576
                  repl_backlog_first_byte_offset:1
                  repl_backlog_histlen:154
                  127.0.0.1:6379>
                  • 可以看到主节点信息有两个从节点connected_slaves:2

                  • 并且从节点的信息也已生成slave0和slave1


                  主从配置踩坑:

                  主从配置服务启动发现info replication的connected_slaves一直是0,查找原因发现主要是下面两个原因:

                  • 主节点配置:bind 0.0.0.0,这样redis才会listen来源于各个ip地址的请求,要不只会处理指定地址来的请求

                  • 因为我的Redis配置后台服务,所以查看日志看到了是和我后台自启动的redis冲突了,将后台自启动的Redis服务kill掉再查看,主从配置正常了


                  Redis的主从配置已经配置完了,接下来我们配置Redis sentinel。

                  6379sentinel配置:去掉注释和空格查看

                    [root@common redis-5.0.3]# cat sentinel.conf | grep -v "#" | grep -v "^$" > redis_sentinel_26379.conf
                    [root@common redis-5.0.3]# cat redis_sentinel_26379.conf
                    port 26379
                    daemonize yes
                    pidfile "/var/run/redis-sentinel.pid"
                    logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/26379.log"
                    dir "/home/duan/myhome/soft/redis/redis-5.0.3/data"
                    sentinel monitor mymaster 127.0.0.1 6379 2
                    sentinel down-after-milliseconds mymaster 30000
                    sentinel parallel-syncs mymaster 1
                    sentinel failover-timeout mymaster 180000

                    使用Redis sentinel启动:

                      [root@common redis-5.0.3]# redis-sentinel redis_sentinel_26379.conf 
                      [root@common redis-5.0.3]# ps -ef | grep redis-sentinel
                      root 5280 1 0 00:24 ? 00:00:00 redis-sentinel *:26379 [sentinel]
                      root 5295 2983 0 00:24 pts/0 00:00:00 grep --color=auto redis-sentinel
                      [root@common redis-5.0.3]#

                      连接客户端:

                        [root@common redis-5.0.3]# redis-cli -p 26379 
                        127.0.0.1:26379> set hello world
                        (error) ERR unknown command `set`, with args beginning with: `hello`, `world`,
                        127.0.0.1:26379> ping
                        PONG
                        127.0.0.1:26379> info replication
                        # Sentinel
                        sentinel_masters:1
                        sentinel_tilt:0
                        sentinel_running_scripts:0
                        sentinel_scripts_queue_length:0
                        sentinel_simulate_failure_flags:0
                        master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=1
                        127.0.0.1:26379>

                        可以到master相关的信息,ip端口号,从节点的个数以及sentinels的个数。

                        启动sentinel之后查看配置变化:

                          [root@common redis-5.0.3]# cat redis_sentinel_26379.conf 
                          port 26379
                          daemonize yes
                          pidfile "/var/run/redis-sentinel.pid"
                          logfile "/home/duan/myhome/soft/redis/redis-5.0.3/logs/26379.log"
                          dir "/home/duan/myhome/soft/redis/redis-5.0.3/data"
                          sentinel myid 46da8b1dd13d41c1f9dd00a17e19c1237f0150af
                          sentinel deny-scripts-reconfig yes
                          sentinel monitor mymaster 127.0.0.1 6379 2
                          sentinel config-epoch mymaster 0
                          # Generated by CONFIG REWRITE
                          protected-mode no
                          sentinel leader-epoch mymaster 0
                          sentinel known-replica mymaster 192.168.1.106 6381
                          sentinel known-replica mymaster 192.168.1.104 6380
                          sentinel current-epoch 0

                          可以从#Generated by CONFIG REWRITE(配置重写产生的配置)看出:

                          • 发现master的两个从节点,6380,6381

                          • 去掉了我们默认的30000和180000(故障转移时间)以及复制的并行度等的配置

                          • 增加一些配置选举的一些东西


                          相同的步骤我们配置26380和26381,分别启动之后查看info sentinel信息:

                            [root@node1 redis-5.0.3]# redis-cli -p 26380
                            127.0.0.1:26380> info sentinel
                            # Sentinel
                            sentinel_masters:1
                            sentinel_tilt:0
                            sentinel_running_scripts:0
                            sentinel_scripts_queue_length:0
                            sentinel_simulate_failure_flags:0
                            master0:name=mymaster,status=ok,address=192.168.1.102:6379,slaves=2,sentinels=3
                            127.0.0.1:26380>
                             
                            [root@node2 redis-5.0.3]# redis-cli -p 26381
                            127.0.0.1:26381> info sentinel
                            # Sentinel
                            sentinel_masters:1
                            sentinel_tilt:0
                            sentinel_running_scripts:0
                            sentinel_scripts_queue_length:0
                            sentinel_simulate_failure_flags:0
                            master0:name=mymaster,status=ok,address=192.168.1.102:6379,slaves=2,sentinels=3
                            127.0.0.1:26381>


                            三、Java客户端来连接Redis Sentinel

                            客户端如果采用直连的方式,当master挂掉之后客户端是无法收到通知的,所以做不到客户端高可用。下面看一下客户端使用Redis Sentinel实现高可用的流程:

                            1. 遍历Sentinel节点集合获取一个可用Sentinel节点

                            2. Sentinel节点会监控Redis节点,返回redis的master节点

                            3. 通过master节点获取到节点的角色信息

                            4. 通知客户端Redis数据节点的变化


                            Redis为了保证连接的高效,这里的实现并没有使用代理,基本原理如下图:

                            使用Java客户端连接代码示例:

                              JedisSentinelPool sentinelPoll = new JedisSentinelPool(masterName, sentinelSet, poolConfig, timeout);
                              Jedis jedis = null;
                              try {
                              jedis = redisSentinelPoll.getResource();
                              jedis common;
                              } catch (Exception e) {
                              log.error(e.getMessage(), e);
                              } finally {
                              if (jedis != null) {
                              jedis.close();
                              }
                              }


                              四、故障转移

                              前面介绍了主从复制时的手动故障转移,下面我们介绍Redis Sentinel是怎么实现自动故障转移的。


                              Redis Sentinel的故障转移过程

                              • 多个sentinel发现并确认master有问题

                              • 选举出一个sentinel作为领导

                              • 选出一个slave作为master

                              • 通知其余slave成为新的master的slave

                              • 通知客户端主从变化

                              • 等待老的master复活称为新的master的slave

                              实际上就是手动故障转移变成sentinel进行故障发现以及故障自动转移以及通知客户端。


                              故障转移机制

                              redis sentinel可以对redis节点做失败判定和故障转移,通过三个定时任务实现了Sentinel节点对于主节点、从节点、其余Sentinel节点的监控。


                              1. 每10秒每个sentinel对master和slave执行info

                              • 发现slave节点

                              • 确认主从关系


                              2. 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)

                              • 通过sentinel:hello频道交互

                              • 交互对节点的看法和自身信息


                              3. 每1秒每个sentinel对其他sentinel和redis执行ping

                              • 是一个心跳检测的过程


                              主观下线与客观下线

                              Redis Sentinel在对节点做失败判定时分为主观下线和客观下线。

                              主观下线:

                                sentinel down-after-millisenconds <masterName> <timeout>
                                sentinel down-after-millisenconds myMaster 30000
                                • 每个sentinel节点对Redis节点失败的偏见

                                客观下线:

                                  sentinel monitor <masterName> <ip> <port> <quorum>
                                  sentinel monitor myMaster 127.0.0.1 6379 2
                                  • 所有sentinel节点对Redis节点失败达成共识(超过quorum个统一)。sentinel is-master-down-by-addr


                                  领导者选举

                                  当节点下线之后是Sentinel节点的领导者选举过程,原因是只有一个sentinel节点完成故障转移。选举时所有Sentinel节点通过sentinel is-master-down-by-addr命令都希望成为领导者。

                                  领导者选举基本过程说明(raft算法):

                                  1. 每个做主观下线的sentinel节点向其他sentinel节点发送命令,要求将它设置为领导者

                                  2. 收到命令的sentinel节点如果没有同意通过其他sentinel节点发送的命令,那么将同意该请求,否则拒绝

                                  3. 如果该sentinel节点发现自己的票数已经超过sentinel集合半数且超过quorum,那么它将成为领导者

                                  4. 如果此过程有多个sentinel节点成为领导者,那么将等待一段时间重新进行选举

                                  Raft算法是对一致性算法Paxos算法的升级精简版,更易于用于构建实际的系统中。 在分布式系统中,一致性指的是集群中的多个节点在状态上达成一致。但是在现实场景中,由于程序崩溃、网络故障、硬件故障、断电等原因,节点间的一致性很难保证,这样就需要Paxos、Raft等一致性协议。Paxos协议是Leslie Lamport在1990年提出的一种基于消息传递的、具有高度容错特性的一致性算法。但是Paxos有两个明显的缺点:第一个缺点就是Paxos算法难以理解。第二个缺点就是并没有提供构建现实系统的良好基础。

                                  关于Paxos、Raft一致性协议算法目前也已经写完了,大家后续可以关注。


                                  故障转移详情

                                  sentinel领导者节点选举完成后进行故障转移:
                                  1. 从slave节点中选出一个合适的节点作为新的master节点

                                  2. 对上述的slave节点执行slaveof no one命令让其成为master节点

                                  3. 向剩余的slave节点发送命令,让他们成为新的master节点的slave节点,复制规则和parallel-syncs参数有关:

                                      1. 假如现在是一主三从,主挂掉了,选择一个新slave节点成为master,
                                      剩余两个slave复制master。
                                        2. 如果parallel-syncs=2,则两个slave同时执行这个复制操作,
                                        redis还是只生成一份RDB文件,但是会同时去进行RDB文件的发送
                                        和buff数据的发送给两个slave,所以对于master节点会占用很多的网络资源
                                        开销去传输这个RDB文件。
                                          3. 如果parallel-syncs=1,则两个slave复制是一个顺序的过程,
                                          首先是第一个slave复制,然后第二个slave节点完成复制,
                                          减少master节点的资源占用。
                                        • 更新对原来master节点配置为slave,并保持着对其关注,当其恢复后命令它去复制新的master节点


                                        选择合适的slave节点成为master:

                                        1. 选择slave-priority(slave节点优先级)最高的slave节点(比如机器配置最好的slave节点),如果存在则返回,不存在则继续

                                        2. 选择复制偏移量最大的slave节点(复制的最完整),如果存在则返回,不存在则继续

                                        3. 选择runId最小的slave节点(启动最早的节点)


                                        故障转移实现过程我们可以开3个Sentinel节点,使用kill -9 master sentinel pid,然后30s后观察日志的方式来熟悉,主要涉及到redis的主从节点日志和sentinel的主从节点日志。


                                        五、高可用读写分离

                                        从节点有个很重的作用是扩展读能力,Redis sentinel这个场景下读写分离有一个问题是,只会对master节点做故障转移,从节点下线的时候,对salve节点只会做一个主观下线的一个判断,从节点无法完成到客户端感知到一个新的从节点的过程,当一个slave节点挂掉后我们如何做客户端读转移呢?实际上和客户端需要知道的三个消息有关:参考JedisSentinelPool的源码(initSentinels)实现:

                                        1. +switch-master:切换主节点(从节点晋升主节点)

                                        2. +convert-to-slave:切换从节点(原主节点降为从节点)

                                        3. +sdown:主观下线

                                        类似于下图处理逻辑:

                                        但是需要自己实现类似JedisSentinelPool客户端源码的一个过程。监听处理以上三个消息的处理。


                                        推荐阅读

                                        Redis开篇介绍

                                        Redis持久化机制

                                        Redis数据结构与内部编码,你知道多少?

                                        看完本文有收获?请转发分享给更多人

                                        关注「并发编程之美」,一起交流Java学习心得

                                        文章转载自并发编程之美,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                        评论