前言
Redis 作为一种高性能的内存数据库,普遍用于目前主流的分布式架构系统中。为了提高系统的容错率,使用多实例的 Redis 也是必不可免的,但同样复杂度也相比单实例高出很多。本文主要会介绍 Redis 在多机数据库下的三种实现。

主从模式

Redis 的主从模式指的就是主从复制。
用户可以通过 SLAVEOF 命令或者配置的方式,让一个服务器去复制另一个服务器即成为它的从服务器。

主从模式架构
image.png
**

Redis 如何实现主从模式?
Redis 的从服务器在向主服务器发起同步时,一般会使用 SYNC 或 PSYNC 命令。
初次同步
当从服务器收到 SLAVEOF 命令后,会向其主服务器执行同步操作,进入主从复制流程。

  1. 从服务器向主服务器发起SYNC 或 PSYNC 命令
  2. 主服务器执行 BGSAVE命令,生成 RDB 文件,并使用缓存区记录从现在开始的所有写命令
  3. RDB 文件生成完成后,主服务器会将其发送给从服务器
  4. 从服务器载入 RDB 文件,将自己的数据库状态同步更新为主服务器执行 BGSAVE命令时的状态。
  5. 主服务器将缓冲区的所有写命令发送给从服务器,从服务将执行这些写命令,数据库状态同步为主服务器最新状态。

image.png
SYNC 与 PSYNC 的区别
当主从同步完成后,如果此时从服务器宕机了一段时间,重新上线后势必要重新同步一下主服务器,SYNC与 PSYNC命令的区别就在于断线后重复制阶段处理的方式不同。

  • SYNC

从服务器重新向主服务器发起 SYNC命令,主服务器将所有数据再次重新生成 RDB 快照发给从服务器开始同步

  • PSYNC

从服务器重新向主服务器发起 PSYNC命令。主服务器根据双方数据的偏差量判断是否是需要完整重同步还是仅将断线期间执行过的写命令发给从服务器。
明显可以发先 PSYNC 相比 SYNC 效率好很多,要知道同步所有数据是一个非常费资源(磁盘 IO,网络)的操作,而如果只是因为短暂网络不稳定就同步所有资源是非常不值的。因此 Redis 在 2.8 版本后都开始使用 PSYNC 进行复制
PSYNC 如何实现部分重同步?
实现部分重同步主要靠三部分
1. 记录复制偏移量
主服务器与从服务器都会维护一个复制偏移量。

  • 当主服务器向从服务器发送 N 个字节的数据后,会将自己的复制偏移量 +N。
  • 当从服务器收到主服务器 N 个字节大小数据后,也会将自己的复制偏移量 +N。

当主从双方数据是同步时,这个偏移量是相等的。而一旦有个从服务器断线一段时间而少收到了部分数据。那么此时主从双方的服务器偏移量是不相等的,而他们的差值就是少传输的字节数量。如果少传输的数据量不是很大,没有超过主服务器的复制积压缓冲区大小,那么将会直接将缓冲区内容发送给从服务器避免完全重同步。反之还是需要完全重同步的。
2. 复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个先进先出的字节队列,默认大小是 1mb。每当向从服务器发送写命令时,都会将这些数据存入这个队列。每个字节都会记录自己的复制偏移量。从服务器在重连时会将自己的复制偏移量发送给主服务器,如果该复制偏移量之后的数据存在于复制积压缓冲区中,则仅需要将之后的数据发送给从服务器即可。
3. 记录服务器 ID
当执行主从同步时,主服务器会将自己的服务器 ID (一般是自动生成的 UUID ) 发送给从服务器。从服务器在断线恢复后会判断该 ID 是否为当前连接的主服务器。如果是同一个 ID 则代表主服务器没变尝试部分重同步。如果不是同一个 ID 代表主服务有变动,则会与主服务器完全重同步。
具体流程图如下:
image.png

Redis 哨兵模式 (Sentinel)

Redis 主从模式虽然能做到很好的数据备份,但是他并不是高可用的。一旦主服务器点宕机后,只能通过人工去切换主服务器。因此 Redis 的哨兵模式也就是为了解决主从模式的高可用方案。
哨兵模式引入了一个 Sentinel 系统去监视主服务器及其所属的所有从服务器。一旦发现有主服务器宕机后,会自动选举其中的一个从服务器升级为新主服务器以达到故障转义的目的。
同样的 Sentinel 系统也需要达到高可用,所以一般也是集群,互相之间也会监控。而 Sentinel 其实本身也是一个以特殊模式允许 Redis 服务器。
image.png

实现原理
1.Sentinel 与主从服务器建立连接

  • Sentinel 服务器启动之后便会创建于主服务器的 命令连接 ,并订阅主服务器的 sentinel:hello 频道以创建 订阅连接
  • Sentinel 默认会每 10 秒向主服务器发送 INFO 命令,主服务器则会返回主服务器本身的信息,以及其所有从服务器的信息。
  • 根据返回的信息,Sentinel 服务器如果发现有新的从服务器上线后也会像连接主服务器时一样,向从服务器同时创建命令连接与订阅连接。

2.判定主服务器是否下线
每一个 Sentinel 服务器每秒会向其连接的所有实例包括主服务器,从服务器,其他 Sentinel 服务器)发送 PING命令,根据是否回复 PONG 命令来判断实例是否下线。
判定主观下线
如果实例在收到 PING命令的 down-after-milliseconds 毫秒内(根据配置),未有有效回复。则该实例将会被发起 PING命令的 Sentinel 认定为主观下线。
判定客观下线
当一台主服务器被某个 Sentinel 服务器判定为客观下线时,为了确保该主服务器是真的下线, Sentinel 会向 Sentinel 集群中的其他的服务器确认,如果判定主服务器下线的 Sentinel 服务器达到一定数量时(一般是 N/2+1),那么该主服务器将会被判定为客观下线,需要进行故障转移。
3.选举领头 Sentinel
当有主服务器被判定客观下线后,Sentinel 集群会选举出一个领头 Sentinel 服务器来对下线的主服务器进行故障转移操作。整个选举其实是基于 RAFT 一致性算法而实现的,大致的思路如下:

  • 每个发现主服务器下线的 Sentinel 都会要求其他 Sentinel 将自己设置为局部领头 Sentinel。
  • 接收到的 Sentinel 可以同意或者拒绝
  • 如果有一个 Sentinel 得到了半数以上 Sentinel 的支持则在此次选举中成为领头 Sentinel。
  • 如果给定时间内没有选举出领头 Sentinel,那么会再一段时间后重新开始选举,直到选举出领头 Sentinel。

4.选举新的主服务器
领头服务器会从从服务中挑选出一个最合适的作为新的主服务器。挑选的规则是:

  • 选择健康状态的从节点,排除掉断线的,最近没有回复过 INFO命令的从服务器。
  • 选择优先级配置高的从服务器
  • 选择复制偏移量大的服务器(表示数据最全)

挑选出新的主服务器后,领头服务器将会向新主服务器发送 SLAVEOF no one命令将他真正升级为主服务器,并且修改其他从服务器的复制目标,将旧的主服务器设为从服务器,以此来达到故障转移。

Redis Cluster

Redis 哨兵模式实现了高可用,读写分离,但是其主节点仍然只有一个,即写入操作都是在主节点中,这也成为了性能的瓶颈。
因此 Redis 在 3.0 后加入了 Cluster 模式,它采用去无心节点方式实现,集群将会通过分片方式保存数据库中的键值对

节点
一个 Redis 集群中会由多个节点组成,每个节点都是互相连接的,会保存自己与其他节点的信息。节点之间通过 gossip 协议交换互相的状态,以及保新加入的节点信息。
image.png

数据的 Sharding
Redis Cluster 的整个数据库将会被分为 16384 个哈希槽,数据库中的每个键都属于这 16384 个槽中的其中一个,集群中的每个节点可以处 0 个或者最多 16384 个槽。
设置槽指派
通过命令 CLUSTER ADDSLOTS [slot…] 命令我们可以将一个或多个槽指派给某个节点。
如 127.0.0.1:7777> CLUSTER ADDSLOTS 1 2 3 4 5 命令就是将 1,2,3,4,5 号插槽指派给本地端口号为 7777 的节点负责。
设置后节点将会将槽指派的信息发送给其他集群,让其他集群更新信息。
计算键属于哪个槽

  1. def slot_number(key):
  2. return CRC16(key) & 16383

计算哈希槽位置其实使用的是 CRC16 算法对键值进行计算后再对 16383 取模得到最终所属插槽。
也可以使用 CLUSTER KEYSLOT 进行查看。
Sharding 流程

  1. 当客户端发起对键值对的操作指令后,将任意分配给其中某个节点
  2. 节点计算出该键值所属插槽
  3. 判断当前节点是否为该键所属插槽
  4. 如果是的话直接执行操作命令
  5. 如果不是的话,向客户端返回 moved 错误,moved 错误中将带着正确的节点地址与端口,客户端收到后可以直接转向至正确节点

image.png

Redis Cluster 的高可用
Redis 的每个节点都可以分为主节点与对应从节点。主节点负责处理槽,从节点负责复制某个主节点,并在主节点下线时,代替下线的主节点。
image.png
如何实现故障转移
Redis 的每个节点都会定期向其他节点发送 Ping 消息,以此来检测对方是否在线。当一个节点检测到另一个节点下线后,会将其设置为疑似下线。如果一个机器中,有半数以上的节点将某个主节点设为疑似下线,则该节点将会被标记为已下线状态,并开始执行故障转移。

  1. 通过 raft 算法从下线主节点的从节点中选出新的主节点
  2. 被选中的从节点执行 SLAVEOF no one 命令,成为新的主节点
  3. 新的主节点撤销掉已下线主节点的槽指派,并将这些槽指给自己
  4. 新的主节点向集群中广播自己由从节点变为主节点
  5. 新的主节点开始接受和负责自己处理槽的有关命令请求

部署时应采用主从交叉部署的方式,主从不要部署在同一台物理机器上。
image.png
假如第3台机器宕机,则第2台机器的从3将升级为主3
image.png


总结**
本文主要介绍了 Redis 三种集群模式,总结一下
主从模式 可以实现读写分离,数据备份。但是并不是「高可用」的
哨兵模式 可以看做是主从模式的「高可用」版本,其引入了 Sentinel 对整个 Redis 服务集群进行监控。但是由于只有一个主节点,因此仍然有写入瓶颈。
Cluster 模式 不仅提供了高可用的手段,同时数据是分片保存在各个节点中的,可以支持高并发的写入与读取。当然实现也是其中最复杂的。