Redis高可用原理总结
因为所在团队主要负责Infra高可用相关的内容,对项目中所有用到的open source的高可用原理进行了学习。最近学习了一下Redis的高可用原理,这里通过笔记整理一下。
Redis高可用架构
Redis相对于之前的PostgreSQL在高可用方面做的更加成熟,并且实现了分布式的高可用架构,其架构如下图所示:
redis-sentinelRedis的高可用架构主要分为两个集群,一个是Redis Server集群,另一个是Sentinel集群。
Redis Server集群:
-
master/slave(1+N)
模式。Redis采用master/slave的模式作高可用,一个master可以有多个slave,并且slave还可以级联子slave - 异步同步。出于效率的原因,Redis的同步方式采用的
异步同步
。master上数据的变化(包括:客户端写数据,key过期和以及其他的操作)都会被sync到slave上。
Sentinel集群的功能:
- 监控。对master和slave节点作心跳检测,更新其状态
- 通知。当master和slave节点发生故障时,提供API用于通知其他process或者管理者
- 自动Failover。当master出现故障时,能够自动failover,promote最佳的slave作为新的master(先比较优先级,然后比较lag,如果都一样则选择最小runid的slave)
- 提供访问配置。提供master的
ip/port
以及密码信息,用于客户端访问master。
Note:
- Sentinel是采用
Raft
协议选举新的master,因此需要建立奇数个Sentinel- 一个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
解释一下其中配置的含义:
-
sentinel monitor mymaster 127.0.0.1 6379 2
表示sentinel监控mymaster
集群,其当前master为127.0.0.1
,其服务端口为6379
,发生failover需要2
个及以上的Sentinel同意; -
sentinel down-after-milliseconds mymaster 60000
表示master超过60s
健康监测失败,就认为该master down; -
sentinel failover-timeout mymaster 180000
当前failover过程超过180s
,才认为failover失败,重新选举; -
sentinel parallel-syncs mymaster 1
表示同一时间只有一个slave可以跟master进行sync(数字设置大,那么可以sync更快)
当然所有配置都可以通过SENTINEL SET command
在运行时动态修改。
Ok,当我们通过上面的配置启动Sentinel之后,它会做以下的操作:
- 通过配置文件更新
mymaster
集群的信息; - 连接到
mymaster
当前master127.0.0.1:6379
,创建两个连接:- 一个用于发送命令
- 另一个为master的PUB/SUB的channel—
__sentinel__:hello
(所有的Sentinel都会在该channel发送hello信息,包含当前集群的最新配置,新加入的Sentinel可以根据该配置更新自己的配置文件);
- 通过前面跟master建立的连接,发送
INFO
命令,并且解析结果,截取所需信息,更新当前集群的topo信息(下面会有详细介绍); - Sentinel会根据添加的slave信息,同样建立两个连接,用于slave的健康监测,failover以及发布配置更新。
Step 3中INFO
返回的内容如下:
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会运行三个定时器:
- 每10秒每个sentinel会对master和slave执行之前提到的
INFO
命令,用于:
a)发现slave节点
b)确认主从关系 - 每2秒每个sentinel通过
PUB/SUB
的频道(__sentinel__:hello
)交换信息(对节点的"看法"和自身的信息),达成共识。 - 每1秒每个sentinel对其他sentinel和redis节点执行
PING
操作(相互监控),做心跳检测。
一个完整的Failover包含下面的过程:
- 对于第3个定时器,当一个Sentinel发现master心跳检测失败超过
down-after-milliseconds
之后,则会将该master置为SDOWN
(主观下线),并且通过PUB/SUB
广播(使用的gossip); - 如果足够数量的Sentinel认为该master已经down,则会将master状态置为
ODOWN
(客观下线); - 置为
ODOWN
的Sentinel,如果没有给其他leader投过票,则会申请成为candidate,开始请求成为leader,并增加自己的epoch
; - 在leader选举周期结束之前,收到足够数量投票(超过一半,并且满足quorum数量)的节点就会成为Leader从slave中选取最佳的节点;
- 将选取的slave,执行
SLAVE OF NO ONE
命令,将该slave提升为新master; - 将配置的
epoch
+1,表示版本号已经更新,并且在PUB/SUB
频道内广播该配置。
Note:
- 对于上面的第4步,如果定时器结束前,没有节点收到足够票数,则节点会退回follower状态,通过一个随机定时器进行退避,退避过程中没有给其它节点投票,就会又成为candidate。由于是一个随机定时器,所以所有节点都成为candidate的概率比较小,通常一个epoch就可以选出leader;
- 如果leader超过
failover-timeout
时间仍未完成failover,Sentinel集群同样需要重新开始leader选举;- 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了)。
服务发现步骤:
- 提供Sentinel的地址列表,Client会选择从第一个Sentinel尝试连接(对于Kubernetes,只用一个Headless Service就搞定);
- Client通过
SENTINEL get-master-addr-by-name master-name
命令查询到master节点的信息; - 连接到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的状态。
网友评论