β

Redis设计与实现总结——多机数据库的实现

oohcode 105 阅读

复制

在Redis中用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项,让一个服务器去复制(repliacte)另一个服务器,被复制的服务器称为主服务器(master),而对服务器进行复制的服务器被称为从服务器(salve)。
复制功能分为同步(sync)和命令传播(command propagate)两个操作:

redis旧版复制

从服务器初次复制主服务器或者从服务器当前要复制的主服务器和上一次不一样时,RDB文件会完整的传输。在处于命令传播阶段的主从服务器因为网络原因而中断了复制,再次连接上时会重头开始复制。但是第二种情况的效率非常低,很多已经复制过的数据需要再次进行复制。这就是旧版复制功能的缺陷。
新版复制功能为了解决重复复制的问题,提出了一个 PSYNC 命令代替之前的 SYNC 命令。完整的复制与上面的第一种情况初次复制是一样的,部分重同步则用于处理断线后的情况: 断线再连接后,主服务器只发送断线期间的写命令到从服务器。
部分重同步的实现是通过 复制偏移量 :

通过对比主从服务器的复制偏移量,程序可以很容易地知道主从服务器是否处于一致状态:

复制积压缓冲区是一个由主服务器维护的固定长度,先进先出队列,默认大小为1MB。当主从断开连接,再次连接时,从服务器会通过 PSYNC 将自己的复制偏移量 offset 发送给主服务器:

在命令传播阶段,从服务器默认会以每秒一次的频率,祥主服务器发送命令 REPLICONF ACK <replication_offset> , 其中 replication_offset 是当前从服务器的复制偏移量, 这个 心跳检测 的作用如下:

Sentinel

Sentinel(哨岗,哨兵)是Redsi的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线的主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。另外Sentinel还会继续监视已下线的服务器,并在它重新上时,将它设置为新的主服务器的从服务器(降级)。
启动Sentinel可以使用命令: redis-sentinel /path/to/your/sentinel.conf redis-server /path/to/your/sentinel.conf --sentinel , 启动时需要执行一下步骤:

为什么有两个连接?
在Redis目前的发布与订阅功能中,被发送的信息不回保存在Redis服务器里,如果发送信息时,接收信息的客户端不在线或者断线,那么这个客户端就会丢失这条信息。 为了不丢失任何信息,必须专门用一个订阅连接来接收该频道的信息 (原理?)。另外除了订阅频道,Sentinel还必须向主服务器发送命令,以此来与主服务器进行通信,所以Sentinel还必须向主服务器创建命令连接。

Sentinel网络拓扑

Sentinel与主服务器,从服务器及其他Sentinel之间都是彼此连接的:

故障处理

检测主观下线状态

默认情况下Sentinel会以 每秒一次 的频率向所有与它创建了命令连接的实例(包括主服务器,从服务器,其他Sentinel等)发送PING命令, 并通过实例返回的PING命令回复判断是否在线。由于每个Sentinel设置的下线时间标准可能不一样,所以会出现不同的Sentinel认为服务器的状态不一致,所以这种情况称为主观下线状态。

检测客观下线状态

当Sentinel从其他Sentinel那里接收的足够数量的已下线判断之后,Sentinel就会认为将主服务器判定为客观下线状态,并对主服务器执行故障转移操作。

选举领头Sentinel

当主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头的Sentinel,并由领头Sentinel对下线服务器执行故障转移。
选举策略是每个检测到主服务器下线的Sentinel都向其他Sentinel发送想要成为领头的命令,收到命令的Sentinel会将发送命令的Sentinel设置为局部领头,如果一个Sentinel被半数以上的Sentinel设置为局部领头,它就胜出,否则会进行再次选举。

故障转移

选举出领头Sentinel后,领头Sentinel将对已下线的主服务器执行故障转移操作:

集群

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。

节点与槽

Redis集群通常由多个节点(node)组成,开始每个节点都是图例的,它们都处于一个只包含自己的集群中,当要组建一个真正可工作的集群,我们必须将节点连接起来,构成一个包含多个节点的集群。使用 CLUSTER MEET <ip> <port> 命令来完成。另外Redis服务器启动时也可以根据 cluster-enabled 配置选项来判断是否开启集群模式。节点信息保存在 cluster.h/clusterNode 结构中, clusterNode 结构保存了一个节点的当前状态,比如节点的创建时间,节点的名等; clusterNode link 属性是一个 clusterLink 结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区; 每个节点都保存着一个 clusterState 结构,这个结构记录了当前节点的视角下,集群目前所处的状态,例如机器是在线还是下线,集群包含多少节点等。
Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为 16384 (=2048*8)个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。当数据库中的16384个槽有节点在处理时,集群处于一个上线状态(ok);相反地,如果数据库中任何一个槽没有得到处理,那么集群处于下线状态(fail)。
槽指派信息记录在 clusterNode.slots[16384/8] 属性中, numslots 记录了节点负责处理的槽的数量。Redis以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i上的二进制位来判断节点是否负责处理槽i:

节点会把自己处理的槽信息发送给其他集群中的其他节点,因此集群中的每个节点都会知道数据库中16384个槽分别被指派给了集群中哪些节点。
clusterState 结构中的 slots[16384] 数组则更上面的正好反过来,它记录了每个槽是由哪个节点在管理的。之所以会有这两种结构是为了在查找节点管理了哪些槽和槽由哪个节点管理的复杂度都降低了。

集群中的执行命令

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽(使用crc16(key)&16383算法得出槽位置),并检查这个槽是否指派给了自己(clusterState.slots[i]是否为自己):

节点与单机服务器在数据库方面的区别是,节点只能使用0号数据库,而单机Redis服务器则没有这一限制。
节点还会使用 clusterState 结构中的 slots_to_keys 跳跃表来保存槽和键之间的关系,主要目的是方便节点对属于某个或某些槽的所有数据库键进行批量操作。

重新分片

Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目的节点),并且相关槽所属的键值对也会从源节点移动到目的节点。这个过程可以在线进行,在重新分片过程中,集群不需要下线,并且源节点和目的节点都可以继续处理命令请求。
Redis的重新分片操作是由Redis的集群管理软件 redis-trib 负责执行的。迁移过程如下:
redis-trib
在执行第四步迁移的过程中,如果客户端向源节点发送一个与数据库键有关的命令,那么:

关于ASK错误与MOVED错误的区别:

复制与故障转移

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。设置从节点的命令: CLUSTER REPLICATE <node_id>
集群中的每个节点都会定期地祥集群中其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定时间内,向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记位疑似下线(probable fail, PFAIL)。如果一个集群里,半数以上负责处理槽的主节点都将某个主节点X报告为疑似下线,那么这个主节点X将被标记为已下线(FAIL), 将主节点X标记为已下线的节点会向集群广播一条关于主节点X的FAIL消息,所有收到这条FAIL消息的节点都会立即将主节点X标记为下线。
当一个从节点发现自己正在复制的主节点进入了已下线状态,从节点将开始对下线主节点进行故障转移,下面是故障转移执行的步骤:

  1. 复制下线主节点的所有从节点里面,会有一个从节点被选中:选举过程和Sentinel差不多。
  2. 被选中的从节点会执行 SLAVEOF no one 命令,成为新的主节点
  3. 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己
  4. 新的主节点向集群广播一条PONG消息,可以让集群中其他节点立即知道这个节点从从节点变为了主节点,并且这个主节点已经接管了原本由已下线主节点负责处理的槽。
  5. 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。

总结

前面主要讲了Redis在多机数据库下的功能特性,其中复制是实现数据备份,数据可靠性的保证。Sentinel实现高可用性的保证。在3.0版本之前的分布式方案都是自己实现的,然后利用Sentinel进行监控。后来Redis自己实现了集群方案,可以用其默认的集群方案来代替之前的自己实现方案。他们之间是相辅相成的,根据自己的需要进行选择。

参考

  1. Redis集群方案应该怎么做?
  2. 如何部署高可用的Redis集群架构

复制

在Redis中用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项,让一个服务器

作者:oohcode
原文地址:Redis设计与实现总结——多机数据库的实现, 感谢原作者分享。

发表评论