Redis哨兵模式
Redis哨兵模式
前面我们已经了解Redis高并发性,用读写分离的主从架构。但是没有考虑redis的高可用性,主服务的进程死掉了该怎么办?redis作为一个优秀的nosql数据库,已为我们提供了解决方案:哨兵(Sentinel)机制。
redis的哨兵系统可以监测一个或多个主服务以及改主服务对应的从服务。当检测到主服务进程挂掉,哨兵系统会从该主服务对应的从服务中选举出来一个接替主服务的位置。旧的主服务重新连接,被修改为新主服务的从服务。
SentinelState
初始化状态
Sentinel是一个特殊化的redis服务,当Sentinel服务启动,服务会根据配置初始化SentinelState结构如下图,该结构会保存关于Sentinel服务的相关状态。
每个sentinelRedisInstance结构代表一个被Sentinel监视的Redis服务器实例(instance),这个实例可以是主服务器、从服务器,或者另外一个Sentinel。
更新状态
初始化完成之后,对于Sentinel感知主从服务后续会发生的变更,去更新SentinelState结构要从Sentinel会和主从服务建立网络连接开始。
主服务
默认状态下Sentinel会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。
信息包含俩部分:
主服务的信息:关于主服务器本身的信息,包括run_id域记录的服务器运行ID,以及role域记录的服务器角色; 主服务下的从服务信息:关于从服务的ip地址、端口号,offset等相关信息。
通过该信息:
更新主服务的sentinelRedisInstance结构相关信息,例如runId; 更新主服务结构中slaves字段对应字典的从服务sentinelRedisInstance结构,存在即更新,不存在新建。
从服务
默认状态下Sentinel会以每十秒一次的频率,通过命令连接向从服务器发送INFO命令。并通过分析INFO命令的回复来获取从服务服务器的当前信息,去更新相应的从服务sentinelRedisInstance结构。
信息主要包含:
从服务器的运行ID run_id。 从服务器的角色role。 主服务器的IP地址master_host,以及主服务器的端口号master_port。 主从服务器的连接状态master_link_status。 从服务器的优先级slave_priority。 从服务器的复制偏移量slave_repl_offset。
Sentinel集群
一般为了保证一个服务的高可用性,都会从单机版进化到集群版。Sentinel服务也是如此。
Sentinel集群中Sentinel互相之间的发现,是通过redis的pub/sub系统实现的。每隔两秒钟,每个哨兵都会往__sentinel__:hello这个channel里发送一个消息,这时候监视同一个redis服务的Sentinel都可以消费到这个消息,并感知到其他哨兵的存在。
消息内容是:自身host、ip和runid还有对这个master的监控配置。
Sentinel收到消息判断runId与自身:
相同,忽略该条消息; 不相同,去解析消息,并对相应主服务器的sentinelRedisInstance结构进行更新。
高可用
为了感知服务的在线状态,默认状态下,Sentinel会主动以每秒频率向和它建立网络连接的master,slave,Sentinel服务发送ping命令。当没有在规定时间内接受到有效回复:+PONG、-LOADING、-MASTERDOWN中任何一个,则认定服务宕机。
主观宕机(sdown)
在Sentinel.conf配置文件,指定了Sentinel判断实例进入主观宕机所需的时间长度,时间单位为ms,默认值为30000。
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
以上配置,当该配置下的一个Sentinel服务在3000毫秒内,没有收到master服务A的有效回复,就会认定master服务A宕机。Sentinel会修改master服务A所对应的sentinelRedisInstance实例结构,把结构的flags属性修改为SRI_S_DOWN标识,以此来表示这个实例已经进入宕机状态。
down-after-milliseconds的值不仅适用于对master宕机状态,也是适用于该master服务下的所有slave,和监视该master的其他Sentinel。
每一个Sentinel服务在配置sentinel down-after-milliseconds可以不同,所以就会出现SentinelA配置3000ms,SentinelB配置5000ms,在3000ms没有收到有效回复,SentinelA会认为master宕机,而SentinelB不认为master宕机。该情况就被认定为主观宕机。
客观宕机(odwon)
SentinelA发现master主观宕机后,但自己不是很确定master是不是真的宕机。所以就需要主动去询问,也在监视这个master的其他Sentinel兄弟,当一定数量的Sentinel服务都认定master宕机,那认定master客观宕机,开始故障转移。
在Sentinel.conf配置文件
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 192.168.1.108 6379 2
以上配置quorum为2,在5个Sentinel的集群中,有2个Sentinel认定master宕机,即为客观宕机。
宕机认定流程:
Sentinel没有收到有效回复,判定为主观宕机。 询问其他Sentinel情况。其他Sentinel去判定宕机状态后,返回判定消息。 收到其他Sentinel的回复,统计认定主观宕机的Sentinel数量,如果超过quorum配置数,判定为客观宕机。 Sentinel修改master服务A所对应的sentinelRedisInstance实例结构,把结构的flags属性修改为SRI_O_DOWN标识。 开始故障转移。
领头Sentinel
故障转移开始时,只需要一个Sentinel来执行这个工作。所以对于Sentinel集群,就需要选举一个领头Sentinel来做这个事。
选举流程:
每一个Sentinel都有选举权和被选举权,并且在一轮选举中只有一次机会。 每个发现master宕机的Sentinel会发送命令给其他Sentinel,希望把自己设置为局部领头Sentinel。 设置局部领头Sentinel规则为先导先得,Sentinel会把最先收到命令中的runId设为自己的局部领头Sentinel;并返回消息。 收到返回消息并解析,判断自己是否已被设置为局部领头Sentinel。 当一个Sentinel被超过一半Sentinel服务设局部领头Sentinel,则认定该Sentinel为领头Sentinel服务。 如果没有选举出来,则开始重新一轮选举。
故障转移
Sentinel集群选出领头Sentinel之后,开始进行故障转移,主要包含三部分:
选举新的master。 修改slave的复制目标。 旧的master重新上线修改为slave。
选举新的master
Sentinel会从剩下的从服务中选举出一个从服务作为新的master,但是不能随便来,要符合以下选举规则:
排除部分从服务:下线/断线、与主服务断开连接超过10*down-after-milliseconds和五秒内没有回复领头Sentinel的info命令的服务。 选出优先级最高的从服务。 优先级一样,选择复制偏移量大的的服务。 偏移量一样,选择runId小的从服务。
转移
选举出来新的主服务后,领头Sentinel会给其他从服务发送saveOf命令,让它们去复制新的master。当旧的master重新上线之后,被修改从服务去并去复制新的master。
数据丢失
没有任何绝对完美的事情,所以在Sentinel模式在进行主备切换时,会出数据丢失情况。
异步复制
主从服务之间的数据复制是异步进行的,当有一部分命令在写入到主服务后,主服务宕机没来及命令传播到从服务器,那么会丢失这部分命令。
脑裂
当master服务脱离了正常网络被认定为宕机,切换新的master,出现了俩个master,即为所谓的脑裂。但是client还没来及切换master,继续会写入数据,当旧的重新上线后被改为从服务,会清空旧的master服务数据去复制新的master,那么会丢失一部分数据。
解决方案
min-slaves-to-write 1
min-slaves-max-lag 10
上面两个配置可以减少异步复制和脑裂导致的数据丢失
要求至少有1个slave,数据复制和同步的延迟不能超过10秒;如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么master就不会再接收任何请求了
有了min-slaves-max-lag这个配置,一旦slave复制数据和ack延时太长,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低到可控范围内。
如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求。这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失。
上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求
因此在脑裂场景下,最多就丢失10秒的数据。
参考:
中华石杉亿级流量 Redis设计与实现