集群方式

  1. 主从复制(Master-Slave Replication)

    • 主从模式:一个Redis实例作为主节点(Master),零个或多个Redis实例作为从节点(Slave)。主节点处理写请求,从节点通过复制主节点的数据来保持与主节点的数据同步,并且可以承担读请求的分发。这是一种基础的高可用保障手段,但不具备自动故障转移功能。

  2. 哨兵(Sentinel)

    • 哨兵模式:Redis Sentinel 是一个分布式系统,用于监控Redis主从集群,并在主节点故障时自动完成故障转移。Sentinel提供了高可用性解决方案,能够在主节点故障时自动选举新的主节点,并通知从节点和客户端进行切换。

  3. Redis Cluster

    • Redis Cluster:Redis Cluster 是Redis官方提供的分布式解决方案,它将数据分割成多个槽(slot),分布在多个Redis节点上,每个节点都可以处理请求。Redis Cluster支持数据分片(sharding)和节点间的自动数据迁移,从而实现水平扩展。它同时也具备一定程度的容错能力,当某个节点故障时,集群可以自动调整并将这部分数据迁移到其他在线节点。

  4. 代理模式(Proxy-based Clustering)

    • CodisTwemproxy(nutcracker)等:这些是基于代理的Redis集群解决方案,它们作为一个独立的中间层,将客户端的请求分发到后端多个Redis实例上。这些代理层通常负责数据分片和请求路由,使得Redis集群对外表现为一个整体,简化了客户端与集群的交互。

主从复制

流程

建全量 补增量(刚启动时发送全量的RDB文件,同步后发送同步过程缓存的新写的命令)

过程:

  • 建立连接

    • 从服务器连接主服务器,发送SYNC命令;

  • 主库同步全量数据给从库

    • 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;

    • 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;

    • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;

  • 同步缓冲区的写命令

    • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;

    • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;(从服务器初始化完成

  • 后续的同步

    • 主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作

主-从-从

一次全量复制中,对于主库来 说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。

如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生 成 RDB 文件,进行数据全量同步。fork 这个操作会阻塞主线程处理正常请求,从而导致 主库响应应用程序的请求速度变慢。

主从级联模式分担全量复制时的主库压力,“主 - 从 - 从”模式

通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力, 以级联的方式分散到从库上

断开连接

如果主库和从库之间的网络断开,主从库会采用增量复制(2.8之前是全量复制)的方式继续同步。

当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也 会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。

repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己 已经读到的位置。

主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距,将这两个数值之间的命令操作同步给从库就行。

因为 repl_backlog_buffer 是一个环形缓冲区,所以在 缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速 度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库 间的数据不一致。

优缺点

优点:

  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

  • 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成

  • Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。

  • Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。

  • Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据

缺点:

  • Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。

  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。

  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

哨兵机制

哨兵的作用就是监控Redis系统的运行状况。它的功能如下:

  • 监控:主服务器和从服务器是否正常运行。

  • 选主 + 通知:主服务器出现故障时自动将从服务器转换为主服务器。

流程

  • 监控是指哨兵进程在运行时,周期性地给所有的主从库发送 PING 命令, 检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就 会把它标记为“下线状态”;同样,如果主库也没有在规定时间内响应哨兵的 PING 命 令,哨兵就会判定主库下线,然后开始自动切换主库的流程。

  • 选主。主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例,把它作为新的主库。这一步完成后,现在的集群里就有了新主库。

  • 通知。在执行通知任务时,哨兵会把新主库的连接信息 发给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时, 哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。

哨兵做决策的两步:

  • 在监控任务中,哨兵需要判断主库是否处于下线状态

  • 在选主任务中,哨兵也要决定选择哪个从库实例作为主库

主观下线/客观下线

  • 主观下线:

    • 主库:哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状 态。如果哨兵发现主库或从库对 PING 命令的响应超时了,那么,哨兵就会先把它标记 为“主观下线”。

    • 从库:哨兵简单地把它标记为“主观下线”就行了,因为从库的下线影响一般不太大,集群的对外服务不会间断。

  • 客观下线:

    • 当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判 断主库为“主观下线”,才能最终判定主库为“客观下线”。这样一来,就可以减少误判 的概率,也能避免误判带来的无谓的主从库切换。

选主库

哨兵选择新主库的过程称为“筛选 + 打分”。在多个从库 中,先按照一定的筛选条件,把不符合条件的从库去掉。然后,再按照一定的规则, 给剩下的从库逐个打分,将得分最高的从库选为新主库,如下图所示:

筛选

  • 检查从库的当前在线状态

    • 检查从库的状态是否被标记为”主观下线“

  • 判断之前的网络连接状态

    • 使用配置项 down-after-milliseconds * 10。其中,down-after- milliseconds 是我们认定主从库断连的最大连接超时时间。如果在 down-after- milliseconds 毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连 了。如果发生断连的次数超过了 10 次,就说明这个从库的网络状况不好,不适合作为新主库。

打分

三步打分:从库优先级、从库复制进度以及从库 ID 号

  • 优先级最高的从库得分高

    • 可以通过 slave-priority 配置项,给不同的从库设置不同优先级。例如我们可以根据服务器的配置不同,设置不同的优先级。优先级默认100。在选主时, 哨兵会给优先级高的从库打高分,如果有一个从库优先级最高,那么它就是新主库了。如果从库的优先级都一样,那么哨兵开始第二轮打分。

  • 和旧主库同步程度最接近的从库得分高

    • 选择和旧主库同步最接近的那个从库作为主库,这个新主库上就有最新的数据。

    • 从库的 slave_repl_offset 最接近 master_repl_offset,那么它的得分就 最高,可以作为新主库。

    • 旧主库的 master_repl_offset 是 1000,从库 1、2 和 3 的 slave_repl_offset 分别是 950、990 和 900,那么,从库 2 就应该被选为新主库。

  • ID 号小的从库得分高

    • 在优先级和复制进度都相同的情况下,ID 号最小的从库得分最 高,会被选为新主库。

优缺点

优点:

  • 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。

  • 主从可以自动切换,系统更健壮,可用性更高。

缺点:

  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。

哨兵通信

哨兵之间

在主从集群中,主库上有一个名为“__sentinel__:hello”的频道,不同哨兵就是通过 它来相互发现,实现互相通信的。

如图,哨兵 1 把自己的 IP(172.16.19.3)和端口 (26579)发布到“__sentinel__:hello”频道上,哨兵 2 和 3 订阅了该频道。那么 此时,哨兵 2 和 3 就可以从这个频道直接获取哨兵 1 的 IP 地址和端口号。

然后,哨兵 2、3 可以和哨兵 1 建立网络连接。通过这个方式,哨兵 2 和 3 也可以建立网 络连接,这样一来,哨兵集群就形成了。它们相互间可以通过网络连接进行通信

哨兵与从库

哨兵给主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵。

哨兵与客户端

每个哨兵实例也提供 pub/sub 机制,客户 端可以从哨兵订阅消息。哨兵提供的消息订阅频道有很多,不同频道包含了主从库切换过 程中的不同关键事件。

Leader 选举

Leader 选举:通过选举来决定是哪个哨兵执行主从切换的过程。

在 T1 时刻,S1 判断主库为“客观下线”,它想成为 Leader,就先给自己投一张赞成票, 然后分别向 S2 和 S3 发送命令,表示要成为 Leader。

在 T2 时刻,S3 判断主库为“客观下线”,它也想成为 Leader,所以也先给自己投一张赞 成票,再分别向 S1 和 S2 发送命令,表示要成为 Leader。

在 T3 时刻,S1 收到了 S3 的 Leader 投票请求。因为 S1 已经给自己投了一票 Y,所以它 不能再给其他哨兵投赞成票了,所以 S1 回复 N 表示不同意。同时,S2 收到了 T2 时 S3 发送的 Leader 投票请求。因为 S2 之前没有投过票,它会给第一个向它发送投票请求的哨 兵回复 Y,给后续再发送投票请求的哨兵回复 N,所以,在 T3 时,S2 回复 S3,同意 S3 成为 Leader。

在 T4 时刻,S2 才收到 T1 时 S1 发送的投票命令。因为 S2 已经在 T3 时同意了 S3 的投 票请求,此时,S2 给 S1 回复 N,表示不同意 S1 成为 Leader。发生这种情况,是因为 S3 和 S2 之间的网络传输正常,而 S1 和 S2 之间的网络传输可能正好拥塞了,导致投票请 求传输慢了。

最后,在 T5 时刻,S1 得到的票数是来自它自己的一票 Y 和来自 S2 的一票 N。而 S3 除 了自己的赞成票 Y 以外,还收到了来自 S2 的一票 Y。此时,S3 不仅获得了半数以上的 Leader 赞成票,也达到预设的 quorum 值(quorum 为 2),所以它最终成为了 Leader。接着,S3 会开始执行选主操作,而且在选定新主库后,会给其他从库和客户端通 知新主库的信息。

如果 S3 没有拿到 2 票 Y,那么这轮投票就不会产生 Leader。哨兵集群会等待一段时间 (也就是哨兵故障转移超时时间的 2 倍),再重新选举。这是因为,哨兵集群能够进行成 功投票,很大程度上依赖于选举命令的正常网络传播。如果网络压力较大或有短时堵塞, 就可能导致没有一个哨兵能拿到半数以上的赞成票。所以,等到网络拥塞好转之后,再进 行投票选举,成功的概率就会增加。

需要注意的是,如果哨兵集群只有 2 个实例,此时,一个哨兵要想成为 Leader,必须获得 2 票,而不是 1 票。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切 换的。因此,通常我们至少会配置 3 个哨兵实例。这一点很重要,你在实际应用时可不能 忽略了。

Cluster集群

流程

  1. 故障检测:每个Redis节点会定期发送心跳消息给其他节点,以监控集群中其他节点的状态。当某个主节点无法发送心跳或无法响应足够数量的节点时,该节点会被认为是不可达的。

  2. 故障声明:当足够多的节点(超过半数的主节点)认为某个主节点失联,这个节点就会被集群标记为故障状态。这个过程称为"故障声明"。

  3. 选举新的主节点:一旦主节点被确认故障,它的一个或多个从节点将参与选举过程,选出一个新的主节点。选举过程考虑多个因素,如从节点的复制偏移量、运行时间和节点ID等。

  4. 晋升为主节点:选举出的从节点会被晋升为新的主节点。晋升过程中,这个从节点会取消对故障主节点的复制,开始接受客户端的请求。

  5. 更新配置:新的主节点会向集群中的其他节点通报这一变更,集群的配置信息将被更新,以反映主从关系的变化。

  6. 重新配置从节点:故障主节点的其他从节点(如果有的话)会被配置为复制新晋升的主节点,以保持数据的冗余和可用性。

重定向

当新增加Redis节点的时候,Reids会加数据重新分配新的sloat(0~16383/2的14次方)个槽中,随之带来的问题是当数据正在迁移时,如果此时收到客户端新的请求,且请求的key恰好落到了正在迁移的槽中,该如何处理?

Redis Cluster 方案提供了一种重定向机制,所谓的“重定向”,就是指,客户端给一个实 例发送数据读写操作时,这个实例上并没有相应的数据,客户端要再给一个新实例发送操 作命令。

MOVED

当客户端把一个键值对的操作请 求发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就 会给客户端返回下面的 MOVED 命令响应结果,这个结果中就包含了新实例的访问地址。

GET hello:key

(error) MOVED 13320 172.16.19.5:6379

MOVED 命令表示,客户端请求的键值对所在的哈希槽 13320,实际是在 172.16.19.5 这个实例上。通过返回的 MOVED 命令,就相当于把哈希槽所在的新实例的 信息告诉给客户端了。这样一来,客户端就可以直接和 172.16.19.5 连接,并发送操作请求了。

MOVED 重定向命令的使用方法。可以看到,由于负载均衡, Slot 2 中的数据已经从实例 2 迁移到了实例 3,但是,客户端缓存仍然记录着“Slot 2 在 实例 2”的信息,所以会给实例 2 发送命令。实例 2 给客户端返回一条 MOVED 命令,把 Slot 2 的最新位置(也就是在实例 3 上),返回给客户端,客户端就会再次向实例 3 发送 请求,同时还会更新本地缓存,把 Slot 2 与实例的对应关系更新过来

ASK

在上图中,当客户端给实例 2 发送命令时,Slot 2 中的数据已经全部迁移 到了实例 3。在实际应用时,如果 Slot 2 中的数据比较多,就可能会出现一种情况:客户 端向实例 2 发送请求,但此时,Slot 2 中的数据只有一部分迁移到了实例 3,还有部分数据没有迁移。在这种迁移部分完成的情况下,客户端就会收到一条 ASK 报错信息,如下所示:

GET hello:key

(error) ASK 13320 172.16.19.5:6379

这个结果中的 ASK 命令就表示,客户端请求的键值对所在的哈希槽 13320,在 172.16.19.5 这个实例上,但是这个哈希槽正在迁移。此时,客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令。这个命令的意思是,让这个实例允许执行客户端接下来 发送的命令。然后,客户端再向这个实例发送 GET 命令,以读取数据。

Slot 2 正在从实例 2 往实例 3 迁移,key1 和 key2 已经迁移过去,key3 和 key4 还在实例 2。客户端向实例 2 请求 key2 后,就会收到实例 2 返回的 ASK 命令。

ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所 请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令, 然后再发送操作命令。

和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息。所以,在上图 中,如果客户端再次请求 Slot 2 中的数据,它还是会给实例 2 发送请求。这也就是说, ASK 命令的作用只是让客户端能给新实例发送一次请求,而不像 MOVED 命令那样,会更 改本地缓存,让后续所有命令都发往新实例。

优缺点

优点:

  • 自动分片:Redis Cluster自动将数据分布在多个节点上,使得数据管理和扩展更加简单。这对于需要处理大量数据的应用来说是非常有用的。

  • 高可用性:通过使用主从复制和自动故障转移,Redis Cluster能够在节点或网络故障时继续提供服务,提高了系统的整体可用性。

  • 线性扩展性:可以通过增加更多的节点来扩展集群的容量和性能,实现几乎线性的扩展。

  • 无中心架构:Redis Cluster采用的是无中心架构,没有单点故障(SPOF),每个节点都是自治的。

  • 数据重新分片:Redis Cluster支持在线重新分片,允许用户在不停机的情况下,将数据从一个节点迁移到另一个节点。

缺点:

  • 复杂性:与单实例Redis相比,Redis Cluster引入了额外的复杂性,包括节点管理、数据分片和故障转移。这可能会增加运维的难度。

  • 跨槽操作限制:Redis Cluster通过分片来分布数据,这意味着跨多个分片(或槽)的操作是受限的。例如,只有当所有涉及的键都在同一个槽中时,才能执行多键操作(如MSET、SUNION等)。

  • 内存使用增加:每个Redis节点需要维护整个集群的状态,这会增加每个节点的内存使用。

  • 客户端兼容性:不是所有的Redis客户端都支持Redis Cluster模式。使用Redis Cluster可能需要使用特定的客户端库或者对现有客户端进行配置。

  • 数据迁移时的性能影响:在线重新分片和数据迁移可能会对集群性能产生暂时的影响。

Codis

Codis 集群中包含了 4 类关键组件。

  • codis server:这是进行了二次开发的 Redis 实例,其中增加了额外的数据结构,支持 数据迁移操作,主要负责处理具体的数据读写请求。

  • codis proxy:接收客户端请求,并把请求转发给 codis server。

  • Zookeeper 集群:保存集群元数据,例如数据位置信息和 codis proxy 信息。

  • codis dashboard 和 codis fe:共同组成了集群管理工具。其中,codis dashboard 负 责执行集群管理工作,包括增删 codis server、codis proxy 和进行数据迁移。而 codis fe 负责提供 dashboard 的 Web 操作界面,便于我们直接在 Web 界面上进行集群管 理。

对比

  1. 稳定性和成熟度来看,Codis 应用得比较早,在业界已经有了成熟的生产部署。虽然 Codis 引入了 proxy 和 Zookeeper,增加了集群复杂度,但是,proxy 的无状态设计 和 Zookeeper 自身的稳定性,也给 Codis 的稳定使用提供了保证。而 Redis Cluster 的推出时间晚于 Codis,相对来说,成熟度要弱于 Codis,如果你想选择一个成熟稳定 的方案,Codis 更加合适些。

  2. 从业务应用客户端兼容性来看,连接单实例的客户端可以直接连接 codis proxy,而原 本连接单实例的客户端要想连接 Redis Cluster 的话,就需要开发新功能。所以,如果 你的业务应用中大量使用了单实例的客户端,而现在想应用切片集群的话,建议你选择 Codis,这样可以避免修改业务应用中的客户端。

  3. 从使用 Redis 新命令和新特性来看,Codis server 是基于开源的 Redis 3.2.8 开发的, 所以,Codis 并不支持 Redis 后续的开源版本中的新增命令和数据类型。另外,Codis 并没有实现开源 Redis 版本的所有命令,比如 BITOP、BLPOP、BRPOP,以及和与事 务相关的 MUTLI、EXEC 等命令。Codis 官网上列出了不被支持的命令列表,你在使 用时记得去核查一下。所以,如果你想使用开源 Redis 版本的新特性,Redis Cluster 是一个合适的选择。

  4. 数据迁移性能维度来看,Codis 能支持异步迁移,异步迁移对集群处理正常请求的性 能影响要比使用同步迁移的小。所以,如果你在应用集群时,数据迁移比较频繁的话, Codis 是个更合适的选择。

可扩展

集群的可扩展性是我们评估集群方案的一个重要维度,你一定要关注,集群中元数据是用 Slot 映射表,还是一致性哈希维护的。如果是 Slot 映射表,那么,是用中心化的第三方存 储系统来保存,还是由各个实例来扩散保存,这也是需要考虑清楚的。Redis Cluster、 Codis 和 Memcached 采用的方式各不相同。

  • Redis Cluster:使用 Slot 映射表并由实例扩散保存。

  • Codis:使用 Slot 映射表并由第三方存储系统保存。

  • Memcached:使用一致性哈希。

从可扩展性来看,Memcached 优于 Codis,Codis 优于 Redis Cluster。所以,如果实际 业务需要大规模集群,建议你优先选择 Codis 或者是基于一致性哈希的 Redis 切片集群方 案。

问题

主从同步和故障切换可能存在的问题和解决方法