美文网首页operator
Redis的高可用架构笔记

Redis的高可用架构笔记

作者: 大雄good | 来源:发表于2019-04-15 00:41 被阅读0次

    Redis高可用原理总结

    因为所在团队主要负责Infra高可用相关的内容,对项目中所有用到的open source的高可用原理进行了学习。最近学习了一下Redis的高可用原理,这里通过笔记整理一下。

    Redis高可用架构

    Redis相对于之前的PostgreSQL在高可用方面做的更加成熟,并且实现了分布式的高可用架构,其架构如下图所示:

    redis-sentinel

    Redis的高可用架构主要分为两个集群,一个是Redis Server集群,另一个是Sentinel集群。

    Redis Server集群:

    1. master/slave(1+N)模式。Redis采用master/slave的模式作高可用,一个master可以有多个slave,并且slave还可以级联子slave
    2. 异步同步。出于效率的原因,Redis的同步方式采用的异步同步。master上数据的变化(包括:客户端写数据,key过期和以及其他的操作)都会被sync到slave上。

    Sentinel集群的功能:

    1. 监控。对master和slave节点作心跳检测,更新其状态
    2. 通知。当master和slave节点发生故障时,提供API用于通知其他process或者管理者
    3. 自动Failover。当master出现故障时,能够自动failover,promote最佳的slave作为新的master(先比较优先级,然后比较lag,如果都一样则选择最小runid的slave)
    4. 提供访问配置。提供master的ip/port以及密码信息,用于客户端访问master。

    Note:

    1. Sentinel是采用Raft协议选举新的master,因此需要建立奇数个Sentinel
    2. 一个Sentinel集群是可以管理多个Redis Server集群(由于Redis是一个单线程的应用,只能使用单核处理,当业务较大时,可以通过创建多个master/slave集群作负载均衡)

    重要过程和配置

    1. Sentinel初始化和配置

    通常Sentinel可以通过下面指令运行:

    redis-sentinel /path/to/sentinel.conf 
    #or
    redis-server /path/to/sentinel.conf --sentinel
    

    Sentinel的配置格式为sentinel <option_name> <master_name> <option_value>,而一个简单的sentinel.conf如下:

    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 60000
    sentinel failover-timeout mymaster 180000
    sentinel parallel-syncs mymaster 1
    

    解释一下其中配置的含义:

    1. sentinel monitor mymaster 127.0.0.1 6379 2表示sentinel监控mymaster集群,其当前master为127.0.0.1,其服务端口为6379,发生failover需要2个及以上的Sentinel同意;
    2. sentinel down-after-milliseconds mymaster 60000表示master超过60s健康监测失败,就认为该master down;
    3. sentinel failover-timeout mymaster 180000当前failover过程超过180s,才认为failover失败,重新选举;
    4. sentinel parallel-syncs mymaster 1表示同一时间只有一个slave可以跟master进行sync(数字设置大,那么可以sync更快)

    当然所有配置都可以通过SENTINEL SET command在运行时动态修改。

    Ok,当我们通过上面的配置启动Sentinel之后,它会做以下的操作:

    1. 通过配置文件更新mymaster集群的信息;
    2. 连接到mymaster当前master127.0.0.1:6379,创建两个连接:
      • 一个用于发送命令
      • 另一个为master的PUB/SUB的channel—__sentinel__:hello(所有的Sentinel都会在该channel发送hello信息,包含当前集群的最新配置,新加入的Sentinel可以根据该配置更新自己的配置文件);
    3. 通过前面跟master建立的连接,发送INFO命令,并且解析结果,截取所需信息,更新当前集群的topo信息(下面会有详细介绍);
    4. Sentinel会根据添加的slave信息,同样建立两个连接,用于slave的健康监测,failover以及发布配置更新。

    Step 3INFO返回的内容如下:

    127.0.0.1:6379> INFO
    ...
    run_id:27e26bb8d6d1ec66fa5d8a15e080a17dcb6cb002
    ...
    # Replication
    role:master
    connected_slaves:2
    slave0:ip=172.17.0.6,port=6379,state=online,offset=3578171,lag=1
    slave1:ip=172.17.0.8,port=6379,state=online,offset=3578171,lag=1
    ...
    

    根据上面的信息,Sentinel会修改它的配置文件,添加其slave信息,并且修改epoch,用来表示版本号(新加入的节点会发现自己版本号小于集群中的版本号,会用大版本号的配置更新自己的配置文件):

    cat /redis/sentinel.conf
    ...
    sentinel config-epoch mymaster 1
    sentinel leader-epoch mymaster 1
    
    sentinel known-slave mymaster 172.17.0.6 6379
    sentinel known-slave mymaster 172.17.0.8 6379
    sentinel known-sentinel mymaster 172.17.0.7 26379 33b728312e9fbd1323e5b2bcf07ff48aacbc87e4
    sentinel known-sentinel mymaster 172.17.0.12 26379 a21447632bbc6bc602f6532103a0fcdeec470356
    sentinel current-epoch 1
    

    2. Failover过程

    首先说明一下,Sentinel会运行三个定时器:

    1. 每10秒每个sentinel会对master和slave执行之前提到的INFO命令,用于:
      a)发现slave节点
      b)确认主从关系
    2. 每2秒每个sentinel通过PUB/SUB的频道(__sentinel__:hello)交换信息(对节点的"看法"和自身的信息),达成共识。
    3. 每1秒每个sentinel对其他sentinel和redis节点执行PING操作(相互监控),做心跳检测。

    一个完整的Failover包含下面的过程:

    1. 对于第3个定时器,当一个Sentinel发现master心跳检测失败超过down-after-milliseconds之后,则会将该master置为SDOWN(主观下线),并且通过PUB/SUB广播(使用的gossip);
    2. 如果足够数量的Sentinel认为该master已经down,则会将master状态置为ODOWN(客观下线);
    3. 置为ODOWN的Sentinel,如果没有给其他leader投过票,则会申请成为candidate,开始请求成为leader,并增加自己的epoch
    4. 在leader选举周期结束之前,收到足够数量投票(超过一半,并且满足quorum数量)的节点就会成为Leader从slave中选取最佳的节点;
    5. 将选取的slave,执行SLAVE OF NO ONE命令,将该slave提升为新master;
    6. 将配置的epoch+1,表示版本号已经更新,并且在PUB/SUB频道内广播该配置。

    Note:

    1. 对于上面的第4步,如果定时器结束前,没有节点收到足够票数,则节点会退回follower状态,通过一个随机定时器进行退避,退避过程中没有给其它节点投票,就会又成为candidate。由于是一个随机定时器,所以所有节点都成为candidate的概率比较小,通常一个epoch就可以选出leader;
    2. 如果leader超过failover-timeout时间仍未完成failover,Sentinel集群同样需要重新开始leader选举;
    3. epoch机制是Raft保证时间同步的一个机制,详细原理可以参见Raft的论文

    看困了?手动做个failover实验动手验证一下:首先在master节点上执行DEBUG SLEEP 30模拟节点hang住30s,因此Sentinel会进行Failover(为了方便,这里我直接用redis-operator在k8s上部署了一个集群):

    ➜  ~ kubectl exec -it rfr-redisfailover-0 -- redis-cli DEBUG SLEEP 30
    

    查看任意的Sentinel的log:

    kubectl log rfs-redisfailover-64c54477f6-gkqvq -f
    1:X 14 Apr 15:36:47.315 # +sdown master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:47.399 # +odown master mymaster 172.17.0.5 6379 #quorum 2/2
    1:X 14 Apr 15:36:47.399 # +new-epoch 2
    1:X 14 Apr 15:36:47.399 # +try-failover master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:47.401 # +vote-for-leader b864933d1fad0ef1dfb58b4faabf75c97f13d91c 2
    1:X 14 Apr 15:36:47.404 # 33b728312e9fbd1323e5b2bcf07ff48aacbc87e4 voted for b864933d1fad0ef1dfb58b4faabf75c97f13d91c 2
    1:X 14 Apr 15:36:47.404 # a21447632bbc6bc602f6532103a0fcdeec470356 voted for b864933d1fad0ef1dfb58b4faabf75c97f13d91c 2
    1:X 14 Apr 15:36:47.477 # +elected-leader master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:47.477 # +failover-state-select-slave master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:47.536 # +selected-slave slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:47.536 * +failover-state-send-slaveof-noone slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:47.636 * +failover-state-wait-promotion slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:48.453 # +promoted-slave slave 172.17.0.8:6379 172.17.0.8 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:48.453 # +failover-state-reconf-slaves master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:48.541 * +slave-reconf-sent slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:49.494 * +slave-reconf-inprog slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:49.495 * +slave-reconf-done slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:49.570 # -odown master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:49.571 # +failover-end master mymaster 172.17.0.5 6379
    1:X 14 Apr 15:36:49.571 # +switch-master mymaster 172.17.0.5 6379 172.17.0.8 6379
    1:X 14 Apr 15:36:49.571 * +slave slave 172.17.0.6:6379 172.17.0.6 6379 @ mymaster 172.17.0.8 6379
    1:X 14 Apr 15:36:49.572 * +slave slave 172.17.0.5:6379 172.17.0.5 6379 @ mymaster 172.17.0.8 6379
    1:X 14 Apr 15:36:54.593 # +sdown slave 172.17.0.5:6379 172.17.0.5 6379 @ mymaster 172.17.0.8 6379
    1:X 14 Apr 15:37:12.186 # -sdown slave 172.17.0.5:6379 172.17.0.5 6379 @ mymaster 172.17.0.8 6379
    

    如同前面讲的过程,首先sentinel更新了epoch,并且开始选举leader,然后开始选择最佳的slave,然后promote为master,将之前的master降为slave,更新配置信息,再将之前的slave挂在新的master下面。

    附:log的解析格式:<instance-type> <name> <ip> <port> @ <master-name> <master-ip> <master-port>,detail含义:

        +reset-master <instance details> -- 当master被重置时.
        +slave <instance details> -- 当检测到一个slave并添加进slave列表时.
        +failover-state-reconf-slaves <instance details> -- Failover状态变为reconf-slaves状态时
        +failover-detected <instance details> -- 当failover发生时
        +slave-reconf-sent <instance details> -- sentinel发送SLAVEOF命令把它重新配置时
        +slave-reconf-inprog <instance details> -- slave被重新配置为另外一个master的slave,但数据复制还未发生时。
        +slave-reconf-done <instance details> -- slave被重新配置为另外一个master的slave并且数据复制已经与master同步时。
        -dup-sentinel <instance details> -- 删除指定master上的冗余sentinel时 (当一个sentinel重新启动时,可能会发生这个事件).
        +sentinel <instance details> -- 当master增加了一个sentinel时。
        +sdown <instance details> -- 进入SDOWN状态时;
        -sdown <instance details> -- 离开SDOWN状态时。
        +odown <instance details> -- 进入ODOWN状态时。
        -odown <instance details> -- 离开ODOWN状态时。
        +new-epoch <instance details> -- 当前配置版本被更新时。
        +try-failover <instance details> -- 达到failover条件,正等待其他sentinel的选举。
        +elected-leader <instance details> -- 被选举为去执行failover的时候。
        +failover-state-select-slave <instance details> -- 开始要选择一个slave当选新master时。
        no-good-slave <instance details> -- 没有合适的slave来担当新master
        selected-slave <instance details> -- 找到了一个适合的slave来担当新master
        failover-state-send-slaveof-noone <instance details> -- 当把选择为新master的slave的身份进行切换的时候。
        failover-end-for-timeout <instance details> -- failover由于超时而失败时。
        failover-end <instance details> -- failover成功完成时。
        switch-master <master name> <oldip> <oldport> <newip> <newport> -- 当master的地址发生变化时。通常这是客户端最感兴趣的消息了。
        +tilt -- 进入Tilt模式。
        -tilt -- 退出Tilt模式。
    

    3. Client设计Guide Line

    由于Redis的集群会发生failover,因此对于client的行为,redis官方给出了下面的Guide Line(当前基本上所有的Redis的客户端都支持Sentinel了)。

    服务发现步骤:

    1. 提供Sentinel的地址列表,Client会选择从第一个Sentinel尝试连接(对于Kubernetes,只用一个Headless Service就搞定);
    2. Client通过SENTINEL get-master-addr-by-name master-name命令查询到master节点的信息;
    3. 连接到master节点调用ROLE,验证当前节点是否是真正的master,如果不是就从Sentinel中第二个节点重新开始服务发现

    重连处理:
    当Client出现连接超时,或者被user中断之后,Redis建议Client重新进行服务发现

    Sentinel Failover重连:
    当Redis集群发生Failover时,Sentinel会发送CLIENT KILL type normal到Redis节点,强制Client重连。

    访问Slaves:
    Client可以通过SENTINEL slaves master-name查询slaves的信息,并且通过ROLE命令验证当前slave的状态。

    Reference

    1. Sentinel
    2. Replication
    3. Sentinel Clients
    4. Sentinel Spec
    5. Sentinel的一篇blog

    相关文章

      网友评论

        本文标题:Redis的高可用架构笔记

        本文链接:https://www.haomeiwen.com/subject/ycxywqtx.html