一、引言
Redis 概述
在当今的数据存储领域,Redis 占据着十分重要的地位。它是一个内存中的数据存储,凭借其出色的性能和丰富的功能,被数百万开发人员广泛应用于诸多场景之中,已然成为构建高性能、可扩展应用程序的得力工具。
从存储特点来看,Redis 的数据库完全在内存中运作,不过它也会利用磁盘来实现数据的持久性,以此避免数据的意外丢失。与众多键值数据存储相比,Redis 所支持的数据类型更为多样且复杂,涵盖了字符串、散列、列表、集合、排序集以及 JSON 等。这些丰富的数据类型为开发人员在处理不同业务逻辑时提供了极大的便利,能够轻松应对各种各样的数据存储与操作需求。
同时,Redis 具备内置的复制功能,通过该功能可以将数据复制到任意数量的从服务器上。这不仅有助于实现数据的冗余备份,增强了数据的安全性,而且在一定程度上还能进行负载均衡,将读请求合理分配到多个从服务器,减轻主服务器的压力。
另外,Redis 还提供了不同级别的磁盘持久性方案,确保在一些突发情况下(如服务器意外断电、故障等),内存中的数据能够尽可能完整地保存下来,以便后续恢复使用。总的来说,Redis 凭借其独特的优势,在缓存、矢量数据库、文档数据库、流式计算引擎以及消息代理等多个方面都发挥着关键作用,成为众多开发者在项目开发中的优选之一。
二、Redis 哨兵模式介绍
哨兵模式的作用
Redis Sentinel 在不使用 Redis 集群时为 Redis 提供高可用性,这是其核心价值所在。它就像是一位时刻警惕的守护者,承担着多项重要任务。
首先是监控功能,Sentinel 会不断地检查 Master 和 Slave 实例是否运作正常。它通过定期向这些实例发送 PING 命令等方式,来检测它们的网络连接状态以及是否能正常响应请求,以此确定各个实例是否按预期工作。例如,每隔 1 秒(默认时间间隔,可配置)每个哨兵会向主节点、从节点及其余哨兵节点发送一次 PING 命令做一次心跳检测,若在规定时间(由 sentinel down-after-milliseconds 参数指定,默认值为 30 秒)内没有收到相应实例回复的 PING 命令,就会对该实例的状态产生怀疑。
其次是通知功能,当被监控的某个 Redis 节点出现问题时,Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。这使得相关人员或程序能够及时知晓 Redis 实例出现了异常情况,进而可以采取相应的应对措施,比如人工介入查看具体故障原因等。
再者就是自动故障转移功能,这也是哨兵模式非常关键的一个作用。当主节点(Master)不能正常工作时,Sentinel 会进行自动故障迁移操作,它会将失效 Master 的其中一个从节点(Slave)升级为新的主节点,并让失效 Master 的其他从节点改为复制新的主节点。而且当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用新 Master 代替失效 Master,从而保障整个 Redis 服务不会因为主节点故障而中断,确保业务的连续性。
最后,Sentinel 还充当客户端服务发现的权限来源,也就是配置提供程序的角色。客户端连接到 Sentinels 以询问负责给定服务的当前 Redis 主机的地址。如果发生故障转移,Sentinels 将报告新地址,方便客户端能动态地获取到当前可用的 Redis 主节点信息,保证始终能和正确的主节点进行通信交互。
工作原理
监控机制:
哨兵模式下的监控是一个多维度且持续进行的过程。一方面,Sentinel 与 Redis Master Node 之间有着特定的关联配置,在创建哨兵模式时就会指定好这种关系,随后 Sentinel 会从主节点上获取所有从节点的信息。之后,Sentinel 会定时向主节点发送 info 命令获取其拓扑结构和状态信息,同时也会向从节点发送同样的命令来掌握各个从节点的相关情况,比如节点是否存活、数据复制的进度等信息,这个时间间隔通常为每 10 秒(可配置)。
另一方面,基于 Redis 的订阅发布功能,每个 Sentinel 节点会向主节点的 sentinel:hello 频道上发送该 Sentinel 节点对于主节点的判断以及当前 Sentinel 节点的信息,像自身的运行状态、对主节点健康情况的初步判断等内容。同时,每个 Sentinel 节点也会订阅该频道,来获取其他 Sentinel 节点的信息以及它们对主节点的判断。
而最关键的是心跳检测机制,每个 Sentinel 节点每隔 1 秒(默认)会向所有的 Master、Slave 以及其他 Sentinel 节点发送一个 PING 命令,作用是通过心跳检测,检测主从服务器的网络连接状态。如果 Master 节点回复 PING 命令的时间超过 down-after-milliseconds 设定的阈值(比如默认 30 秒),则这个 Master 会被 Sentinel 标记为主观下线,修改其 flags 状态为 SRI_S_DOWN。不过,主观下线只是单个 Sentinel 基于自身检测做出的判断,为了避免误判,后续还需要结合其他 Sentinel 的判断来进一步确认。
故障转移过程:
当某个 Sentinel 将主节点标记为主观下线后,如果这个主观下线的节点是主节点(Master),该 Sentinel 会向其余所有的 Sentinel 发送 sentinel is-master-down-by-addr 消息,询问其他 Sentinel 是否同意该 Master 下线。其他 Sentinel 收到命令之后,会根据发送过来的 IP 和端口检查自己判断的结果,回复自己是否认为该 Master 节点已经下线了。
然后,Sentinel 会收集这些回复,如果同意 Master 节点进入主观下线的 Sentinel 数量大于等于配置文件中设定的 quorum 值(法定人数,用于判定客观下线的哨兵数量要求),则 Master 会被标记为客观下线,即认为该节点确实已经不可用了。
一旦主节点被判定为客观下线,哨兵系统会进行协商,选举出一个 Sentinel 作为领导者(Leader Sentinel),选举过程通常基于节点的优先级、延迟、网络稳定性以及其他因素进行权衡,各个 Sentinel 节点之间通过投票来决定谁应该担任 Leader Sentinel,投票机制有点类似于 Raft 算法,需要拿到半数以上赞成票且票数大于等于 quorum 值的 Sentinel 才能成为领导者。
选出 Leader Sentinel 之后,就由它来主导故障转移操作。它会按照一定的规则从多个从节点中选择一个合适的从节点升级为新的主节点,规则大致包括先过滤掉所有不健康的(如下线或者断线、没有回复哨兵 PING 响应的)从节点,然后优先选择 slave-priority 从服务器优先级最高的;若优先级相同则选择复制偏移量最大的从服务器(也就是复制数据最完整的);若偏移量相同则按照运行从服务器 ID 进行排序,选出其中运行 ID 最小的从服务器。
确定好新的主节点后,Leader Sentinel 会向其发送 slaveof no one 命令让其成为主节点,接着向剩余的从节点发送 slaveof + 新主节点 IP + 新主节点端口命令,让它们成为新主节点的从节点。同时,哨兵节点还会对旧主节点保持关注,当其恢复后发送 slaveof + 新主节点 IP + 新主节点端口命令将其设置为从节点,并让其去同步数据。并且,整个过程中,哨兵会将新主节点的信息通过发布订阅等方式通知给客户端,以便客户端更新连接地址,继续和新的主节点进行交互。
三、Redis 哨兵模式如何运行
运行哨兵的方式
在 Redis 中运行哨兵模式,有以下两种常用的启动方式。
其一,若你正在使用 redis-sentinel 可执行文件(或者存在一个指向 redis-server 可执行文件的具有该名称的符号链接),那么可以通过如下命令行来运行哨兵:
|---------------------------------------|
| redis-sentinel /path/to/sentinel.conf |
其二,也能够直接采用 redis-server 可执行文件,并以 Sentinel 模式启动它,对应的命令如下:
|------------------------------------------------|
| redis-server /path/to/sentinel.conf --sentinel |
这两种启动方法的本质是一样的,最终都能让 Redis 进入哨兵模式运行。不过需要着重强调的是,运行 Sentinel 时,配置文件的使用是强制要求的。因为系统要依靠这个配置文件来保存其当前状态,以便在重启的时候能够重新加载这些信息。要是没有给出配置文件,又或者配置文件的路径不具备可写权限,那么 Sentinel 将会拒绝启动。
另外,Sentinels 在默认情况下,会运行侦听到 TCP 端口 26379 的连接。所以,为了保障 Sentinels 能够正常工作,服务器的 26379 端口必须保持开放状态,使其可以接收来自其他 Sentinel 实例的 IP 地址的连接。否则,Sentinels 之间无法进行通信交流,也就没办法针对后续的操作达成一致意见,像故障转移、切主等操作就永远不会被执行了。
哨兵配置详情
配置文件示例与解读
Redis 源代码发行版中包含了一个名为 sentinel.conf 的文件,它自身带有说明示例,能够用于配置 Sentinel。不过,典型的最小配置文件通常是如下这般模样:
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 resque 192.168.1.3 6380 4 sentinel down-after-milliseconds resque 10000 sentinel failover-timeout resque 180000 sentinel parallel-syncs resque 5 |
在这样的配置文件里,我们只需要指定要监视的主机即可,并且要为每个分离的主机(每个主机可能配备有任意数量的副本)赋予不同的名称,对于副本是无需特意指定的,因为 Sentinel 具备自动发现副本的能力。例如上述配置,基本上就是在监视两组 Redis 实例,每组实例都是由一个 master 和数量不定的副本所组成,一组实例命名为 mymaster,另一组则叫做 resque。
下面详细解读下 sentinel monitor 语句里各个参数的含义,其语法格式为 sentinel monitor <master-name> <ip> <port> <quorum>。以配置中的 sentinel monitor mymaster 127.0.0.1 6379 2 这一行举例来说:
mymaster:代表的是被监控的主节点的名称,这是一个自定义的标识,方便后续在各种操作以及配置中对该主节点进行引用。
127.0.0.1:明确指出了主节点所在的 IP 地址,告知 Sentinel 去哪里寻找对应的主节点实例。
6379:对应的是主节点运行时所监听的端口号,通过这个端口,Sentinel 才能与主节点建立连接并进行交互通信,发送诸如检测心跳、获取状态信息等各类命令。
2:这里的 quorum 参数,指的是法定人数,它代表着需要就主节点不可达这一事实达成一致的哨兵数量。只有当达到这个数量的哨兵都认为主节点出现问题,无法正常访问时,才会真正将主节点标记为失败,并且在满足相应条件的情况下,最终启动故障转移的相关流程。
法定人数相关规则
法定人数(quorum)在 Sentinel 的故障检测以及实际执行故障转移的过程中,起着至关重要的判定作用,有着明确的规则要求。
在检测故障阶段,quorum 用于判定主节点是否处于不可达或者出现错误的状态。例如,当配置的 quorum 值为 2 时,如果有两个哨兵同时发现它们向主节点发送 PING 命令后,在规定的 down-after-milliseconds 时间内没有收到有效的回复(意味着主节点可能出现故障了),那么这两个哨兵就会初步判断主节点出现问题。不过,这仅仅是基于部分哨兵的主观判断,此时主节点被标记为主观下线(Subjectively Down,简称 SDown)。
而在实际执行故障转移阶段,情况则有所不同。即使有足够数量的哨兵判定主节点主观下线了,但要真正去执行故障转移操作,还需要满足另一个条件,那就是其中一个哨兵要被选为故障转移的领导者(Leader Sentinel),并且这个选择过程需要得到大多数哨兵(这里指的是超过一半的哨兵数量)的投票授权才行。
为了更清晰地理解,举个例子来说明。假设现在部署了 5 个 Sentinel 进程,并且给定主节点的仲裁(quorum)设置为值 2,那么会出现以下情况:
当有两个哨兵同时同意主节点无法访问时,这两个哨兵中的某一个会尝试启动故障转移流程,也就是先发起成为领导者并执行故障转移的请求。
然而,只有当至少有三个哨兵能够正常通信、处于可访问状态时(也就是达到了大多数哨兵的条件),这个故障转移的请求才会被授权通过,实际的故障转移操作才会真正开始执行。
相反,如果在故障期间,大部分的 Sentinel 进程之间无法进行通信(也就是出现了少数分区的情况,导致无法形成多数派的共识),那么 Sentinel 是永远不会启动故障转移操作的。所以说,quorum 参数的合理设置,能够在一定程度上平衡对主节点故障检测的敏感度以及故障转移操作的严谨性,帮助我们根据实际的部署环境和需求,来灵活调整 Sentinel 的行为策略。
四、Redis 哨兵模式提供的 API
常用 API 介绍
获取当前主机地址 API:
在 Redis 哨兵模式的实际应用中,常常需要知道当前负责给定服务的 Redis 主机地址,尤其是在面对可能发生的故障转移等情况时。这时,就可以通过 SENTINEL get-master-addr-by-name 这个 API 来获取相关信息。例如,我们有一个名为 mymaster 的 Redis 主节点服务,在命令行中输入如下命令:
|------------------------------------------------------------|
| 127.0.0.1:5000> SENTINEL get-master-addr-by-name mymaster |
执行后,它会返回对应的主机地址和端口信息,像下面这样:
|--------------------------|
| 1) "127.0.0.1" 2) "6379" |
其作用在于,客户端或者其他需要与 Redis 主节点交互的应用程序,无需提前知晓主节点的具体地址,只需借助这个 API 向 Sentinel 询问,就能动态地获取到准确的主节点地址,从而顺利建立连接进行数据的读写等操作,极大地提升了整个 Redis 服务使用的灵活性和可靠性,保障业务能够持续稳定地运行,不受节点变动的影响。
查询主机状态相关 API:
在日常对 Redis 哨兵模式的监控和维护过程中,需要及时了解主节点的运行状态以及与之相关的副本、其他哨兵等信息。比如,sentinel master 命令可以用来检查正在被监控的主节点是否运行良好。例如,我们通过以下方式来操作:
|---------------------------------------------------------------|
| $ redis-cli -p 5000 127.0.0.1:5000> sentinel master mymaster |
通过这个命令可以获取主节点当前基本的状态信息。
而若想进一步探索更多相关信息,还可以使用 SENTINEL replicas mymaster 命令,它能够提供连接到主服务器的副本的类似信息,帮助我们知晓副本节点的数量、状态等情况,以便判断数据复制是否正常进行,有没有副本节点出现异常等问题。
另外,SENTINEL sentinels mymaster 命令则主要用于获取有关其他哨兵的信息,比如当前有多少个哨兵在监控这个主节点,各个哨兵的运行状态等内容。通过这些命令的综合运用,运维人员或者开发人员可以全方位地掌握 Redis 集群中主节点以及周边相关节点的实时状态,便于及时发现潜在问题并采取对应的解决措施。
其他实用 API:
除了上述常见的 API 之外,Redis 哨兵模式还提供了一些其他在日常运维等方面非常实用的 API。例如 sentinel reset 命令,它可以重置某个主节点的相关状态信息,常用于在一些配置调整或者故障修复后,对主节点的部分统计、标记等状态进行初始化操作,使其恢复到一个相对初始的干净状态,方便后续重新进行准确的监控和判断。
还有 sentinel failover 命令,这个命令可以手动触发一次故障转移操作。在某些特定场景下,比如我们提前知道主节点即将进行维护或者已经出现了一些潜在问题,但还未达到自动故障转移的条件时,运维人员可以通过这个命令手动让 Sentinel 执行故障转移流程,选择合适的从节点升级为主节点,保障 Redis 服务的可用性和数据的正常流转。比如对于名为 mymaster 的主节点,执行以下命令:
|-------------------------------------------------------------------|
| $ redis-cli -p 26379 127.0.0.1:26379> sentinel failover mymaster |
就可以启动对应的手动故障转移过程了。这些 API 从不同角度辅助我们更好地管理和维护 Redis 哨兵模式下的集群,确保整个系统稳定高效地运行。
五、Redis 哨兵模式隐藏的风险
单点故障风险
在 Redis 哨兵模式中,尽管其设计初衷是为了提升 Redis 的高可用性,减少因主节点故障等问题导致的服务中断情况,但它自身也存在着单点故障的风险隐患。
哨兵节点在整个架构里扮演着极为关键的角色,承担着监控 Redis 主从节点状态、发起故障转移等重要任务。例如,若某个哨兵节点本身出现硬件故障(像服务器硬盘损坏、内存故障等),或者软件层面出现问题(如程序崩溃、遭遇恶意软件攻击等),又或者所在的网络环境出现异常(网络连接中断、网络延迟过高长时间无法恢复等),就可能致使该哨兵节点无法正常工作。
而一旦有部分哨兵节点失去作用,对于那些依赖法定人数(quorum)来判定主节点是否故障以及进行故障转移的情况来说,可能会产生严重影响。比如,配置的法定人数是 3,原本有 5 个哨兵节点正常运行,能较好地保证在主节点出现问题时准确判定并执行故障转移操作。但要是其中 2 个哨兵节点出现故障,剩余 3 个哨兵节点在面对主节点故障时,虽然刚好达到法定人数能判定主节点客观下线,可在后续选举领导者(Leader Sentinel)以及执行故障转移的过程中,如果再有 1 个哨兵节点出现临时的网络抖动等小问题,就可能导致无法成功选出领导者,进而故障转移操作无法顺利执行,整个系统就会陷入故障状态,影响 Redis 服务的正常提供,对系统的可靠性构成较大威胁。
切换带来的延迟问题
当 Redis 的主库出现故障时,哨兵节点会自动介入进行故障转移操作,这个过程涉及到一系列的步骤,其中就包括选举新的主库。而在这个切换的过程中,往往难以避免地会产生网络延迟问题。
首先,哨兵节点之间需要相互通信、交换信息,来共同判定主节点是否真的出现故障,这个信息同步的过程会消耗一定时间。比如,各个哨兵节点需要发送如 "sentinel is-master-down-by-addr" 消息来询问、确认主节点的状态,网络状况良好时这个消息传递和回复会相对较快,但如果网络带宽有限或者存在网络拥塞情况,消息的传递就会出现延迟,进而拉长整个判定主节点故障的时长。
随后,在确定主节点客观下线后,要进行选举领导者(Leader Sentinel)以及选择合适的从节点升级为新主库等操作,这些过程同样依赖于网络通信来完成投票、指令传达等环节。而且新主库确定后,还需要通知客户端更新连接地址,让客户端重新与新主库建立连接,客户端获取到新地址并重新发起连接请求这个过程也会受到网络因素影响。在网络延迟较高的情况下,就会导致客户端在一段时间内无法正常进行写操作或者读操作(如果之前的读请求是指向旧主库且未及时切换的话),系统性能会因此出现明显的下降,影响业务的正常响应速度,尤其是对于那些对实时性要求较高的应用场景,这种延迟带来的影响可能会更为突出。
数据丢失风险
Redis 采用的是异步复制机制,这一特性在正常运行情况下能保障数据的高效复制与读写性能,但在一些特殊场景下,却容易引发数据丢失的风险,在 Redis 哨兵模式下同样需要重点关注这个问题。
例如在网络分区的情况下,也就是部分节点之间的网络连接出现中断、彼此无法正常通信时,可能会出现 "脑裂" 现象。具体而言,某个主节点所在的机器突然脱离了正常的网络,与其他从节点不能连接了,但实际上主节点还在运行着。这时,哨兵节点由于无法与该主节点通信,可能会认为主节点宕机了,进而开启选举,将其他从节点切换成新的主节点。然而,可能客户端还没来得及切换到新的主节点,依旧向旧主节点写入数据,这些数据后续就会丢失,因为当旧主节点再次恢复网络连接,重新加入集群时,会被作为一个从节点挂到新的主节点上去,自身原本的数据会被清空,重新从新的主节点复制数据。
为了缓解这种因异步复制以及类似 "脑裂" 情况导致的数据丢失问题,可以采用如 "min-replicas-to-write 1""min-replicas-max-lag 10" 这样的配置。其要求是至少有 1 个从节点,并且数据复制和同步的延迟不能超过 10 秒。如果所有的从节点数据复制和同步的延迟都超过了 10 秒钟,那么主节点就不会再接收任何请求了。通过这样的配置,一定程度上可以减少数据丢失情况的发生,比如在从节点复制数据和确认(ack)延时太长时,就认为可能主节点宕机后损失的数据太多了,便拒绝写请求,将主节点宕机时由于部分数据未同步到从节点导致的数据丢失降低到可控范围内。不过,这种配置也存在一定的弊端,比如当从节点数量不足或者出现多个从节点同时出现延迟等异常情况时,主节点可能会频繁拒绝写请求,影响业务的正常写入操作,这是一种为了保障数据安全性而在可用性方面做出的权衡。
六、Redis 哨兵模式实现算法
法定人数算法
在 Redis 哨兵模式中,法定人数(quorum)是一个关键概念,对于触发故障转移以及后续授权等环节起着决定性作用。
从触发故障转移角度来看,quorum 用于判定主节点是否处于不可达或者出现错误的状态。例如,每个哨兵会定时向主节点发送 PING 命令做心跳检测,若在规定的 "down-after-milliseconds" 时间内,达到 quorum 数量的哨兵都没有收到主节点回复的 PING 命令,就会认为主节点出现问题,此时主节点被标记为主观下线(SDown)。不过这只是基于部分哨兵的主观判断,像配置的 quorum 值为 3 时,需要有 3 个哨兵同时觉得主节点有故障,才会初步判定主节点主观下线。
而在实际执行故障转移阶段,要求会更加严格。即便有足够数量的哨兵判定主节点主观下线了,要真正去执行故障转移操作,其中一个哨兵要被选为故障转移的领导者(Leader Sentinel),并且这个选择过程需要得到大多数哨兵(超过一半的哨兵数量)的投票授权才行。比如有 5 个哨兵进程,quorum 设置为 3,当 3 个哨兵判定主节点主观下线后,想要执行故障转移的那个哨兵必须获得至少 3 个(多数派)哨兵的授权才能成为领导者,进而主导故障转移工作。如果 quorum 配置为 5(大于等于多数派),那么必须所有 5 个哨兵都同意授权,才能执行故障转移。
假设在一个实际部署场景中,有 7 个哨兵在监控 Redis 主从节点,quorum 设置为 4。当主节点出现网络故障等问题时,若有 4 个哨兵同时检测到主节点无响应,主节点就会被标记为主观下线。接着,这 4 个哨兵中的某一个尝试发起故障转移请求,只有当至少再有 3 个(一共达到 7 个中的多数,即 4 个)哨兵认可这个请求并授权时,该哨兵才能正式成为领导者去执行故障转移,选择合适的从节点升级为新主节点,完成后续一系列切换操作。法定人数的合理设置,能够依据实际的网络环境、对故障敏感度的要求等因素,灵活调整哨兵模式在故障检测以及故障转移方面的行为逻辑,保障 Redis 服务的高可用性。
配置时期相关算法
在 Redis 哨兵模式的故障转移过程中,配置纪元(configuration epoch)有着重要意义。当哨兵获得多数人授权启动故障转移时,它会为正在故障转移的主机获得一个唯一的配置纪元,这个配置纪元本质上就是一个数字,相当于一个版本号,会在故障转移完成后用于版本化新配置。
由于这个配置纪元是经过大多数哨兵同意分配给执行故障转移的特定哨兵的,所以其他哨兵无法使用相同的版本号,这就保证了每次故障转移所产生的新配置都有唯一的版本标识。例如,在一个包含多个哨兵监控的 Redis 集群里,主节点出现故障需要进行故障转移,哨兵 A 被选举为领导者且获得授权开始执行故障转移,此时它被分配了配置纪元为 5,那么在这次故障转移期间及后续相关配置更新时,都是基于这个版本号 5 来进行的。
同时,哨兵还有相应的时间规则来避免同时多次尝试故障转移同一主机的情况。具体来说,有一个规则是如果哨兵投票给另一个哨兵来进行给定主机的故障转移,它将等待一段时间来再次尝试故障转移同一主机,这个延迟时间是可以在 "sentinel.conf" 中配置的 "2 * failover-timeout"。比如,"failover-timeout" 配置为 90 秒,那么这个等待时间就是 180 秒。这意味着哨兵们不会无序地同时尝试对同一主机进行故障转移,而是按照先后顺序,第一个请求授权的哨兵先尝试,如果失败了,其他哨兵会在规定的等待时间之后再尝试,依次类推。
通过这样的机制,Redis Sentinel 保证了活性属性,只要大多数的哨兵能够正常通信交流,最终一定会授权一个哨兵在主服务器关闭时进行故障转移操作。而且还保证了每个哨兵将使用不同的配置时期对同一主机进行故障转移的安全属性,避免了因配置混乱而导致的各种潜在问题,确保整个 Redis 集群在故障转移过程中的稳定性和数据的正确流转。
配置传播算法
当故障转移成功完成后,为了让整个 Redis 集群中的各个哨兵节点都能知晓并更新相关信息,哨兵会通过广播新配置的方式来实现配置传播。
具体而言,故障转移成功的标志之一是哨兵能够将 "REPLICAOF NO ONE" 命令发送到选定的副本节点,并且稍后在主节点的 "INFO" 输出中观察到已经切换到新的主节点状态。一旦满足这个条件,即使副本的重新配置工作还在进行当中,也会认为故障转移已经成功,此时所有的哨兵都需要开始报告新的配置信息了。
而传播新配置主要是依靠 Redis 的 Pub/Sub 消息机制,每个哨兵都使用这个机制在主机以及所有副本所在的相关频道中不断广播其主机配置版本。所有的哨兵同时也都在等待接收消息,通过对比其他哨兵宣传的配置版本号来决定是否更新自己的配置。这些配置信息都是在 "sentinel:hello" 这个 Pub/Sub 频道中进行广播的。
由于每个配置都带有不同的版本号(也就是配置纪元),并且遵循大版本号总是胜过小版本号的原则,所以整个配置传播和更新的过程是有序且可靠的。例如,一开始所有哨兵都认为主节点 mymaster 处于 192.168.1.50:6379 这个地址,其配置版本为 1。一段时间后,某个哨兵被授权使用版本 2 进行故障转移,并且故障转移成功了,它就会开始广播新的配置,比如新的主节点地址变成了 192.168.1.50:9000,版本号为 2。其他的哨兵接收到这个带有更大版本号的新配置消息后,就会相应地更新自己所记录的主节点配置信息,从而保证整个集群中的所有哨兵对于主节点的配置认知最终能够达成一致,实现配置的收敛,维持集群的稳定运行状态。
分区下一致性算法
在实际的网络环境中,可能会出现网络分区的情况,此时涉及 Redis 实例、哨兵实例、客户这三者之间的系统行为一致性就变得尤为重要,其背后有着特定的算法逻辑来保障整体的协调运行。
当网络分区发生时,不同分区内的节点之间无法正常通信,就可能出现各种不一致的情况。比如,在一个简单的网络场景里,有 3 个节点,每个节点运行一个 Redis 实例和一个哨兵实例,初始状态是 Redis 3 是主机,Redis 1 和 2 是副本。如果发生分区隔离了旧主机(Redis 3 所在分区),那么其他分区中的哨兵 1 和 2 会开始故障转移,将哨兵 1 提升为新主机,此时哨兵 1 和 2 会拥有主服务器的新配置。然而,处于不同分区的哨兵 3 仍然保留着旧的配置。
对于客户端来说,如果有客户端与旧主分区(仍连接着 Redis 3),在分区期间客户端可能依旧向旧主机写入数据。但当分区重新恢复连接时,Redis 3 会变成 Redis 1 的副本,分区期间写入的数据就会丢失。针对这种情况,要依据具体的应用场景和需求来决定是否允许这样的数据丢失情况发生。
如果将 Redis 用作缓存,一定程度上允许客户端继续向旧主机写入数据(即使数据后续会丢失)可能影响不大;但要是将 Redis 用作数据存储,这种情况就不太能接受了,此时可以通过一些 Redis 配置选项来尽量减少数据丢失风险,比如配置 "min-replicas-to-write 1" 和 "min-replicas-max-lag 10"。这意味着当 Redis 实例充当主服务器时,如果它不能写入至少一个副本(要么副本断开连接,要么副本没有在规定的 10 秒内发送异步确认),主服务器就会停止接受写入操作。按照这样的配置,上述例子中的 Redis 3 在 10 秒后就会变得不可用,等到分区愈合时,哨兵 3 的配置会收敛到新配置,客户端也能够获取到有效配置并继续正常工作。
总体而言,Redis+Sentinel 作为一个整体是一个最终一致的系统,在分区等特殊情况下遵循最后一次故障转移