Redis设计与实现--多机数据库的实现


1. 复制

Redis支持主从复制功能,当Redis主服务器更新数据时能将数据同步到Redis从服务器.

通过命令: slaveof 主服务器ip地址, 形成主从关系, 完成复制.

旧版:Redis 2.8 以前的版本;
新版:从Redis 2.8开始使用的版本.

1.1 旧版复制实现

复制分为同步(sync)和命令传播(command propagate)两个操作.

同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态.
命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态.

  1. 同步
    从向主服务器发送sync命令;
    主服务器收到sync命令执行BGSAVE,生成RDB文件,缓冲区同时记录从现在开始执行的所有命令;
    主服务器执行完毕BGSAVE命令,会将RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,同步至主服务器状态;
    主服务器发送缓冲区的命令给从服务器,从服务器执行当前命令,保持数据最终一致.
    同步

  2. 命令传播
    主服务器会将客户端对它执行的写命令,发送给从服务器,使主从服务器状态保持一致.

旧版复制的缺点:
从服务器断线后,并在重连之后,从服务需要完全重新复制主服务器的数据,效率非常低.

旧版复制的命令是SYNC,这是非常消耗资源的操作,理由如下:

  1. 主服务器需要执行BGSAVE来生成RDB文件,这个操作会消耗主服务器大量的CPU,内存和磁盘I/O资源.
  2. 主服务器将生成的RDB发送给从服务器,这个操作会消费主从服务器大量的网络资源.
  3. 接收到RDB文件的从服务器需要载入主服务器发来的RDB文件,并且在载入期间,从服务器会处于阻塞状态.

1.2 新版复制实现

使用PSYNC命令替代了SYNC.该命令具有 完整重同步 和 部分重同步 两种模式.
其中完整重同步用于处理初次复制情况,和SYNC类似
而部分重同步则用于断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据更新至主服务器当前所处的状态.

部分重同步

部分重同步通过复制偏移量,复制积压缓冲区,服务器运行ID三个部分来实现.

其中复制偏移量+复制积压缓冲区其实类似于滑动窗口.
服务器运行ID其实就是身份确认,用来说明当前服务器就是从服务器断线前连接的那个服务器.

PSYNC命令的实现

PSYNC命令的执行过程

1.3 复制步骤

步骤1:设置主服务器的地址和端口
步骤2:建立套接字连接
步骤3:发送PING命令
步骤4:身份验证
步骤5:发送端口信息
步骤6:同步
步骤7:命令传播

1.4 心跳检测

三个作用:

  1. 检测主从服务器的网络连接状态;
  2. 辅助实现min-slaves选项;(用于防止主服务器在不安全的情况下执行写命令,比如三个从服务器的延迟都大于10秒时,主服务器将拒绝执行写命令)
  3. 检测命令丢失.(类似于滑动窗口的作用)

2. Sentinel

Sentinel是Redis高可用解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求.

Sentinel本质上是运行在特殊模式下的Redis服务器,只是端口和命令表等不同.

Sentinel会创建两个连向被监视主/从服务器的异步网络连接,一个是命令连接,一个是订阅连接.Sentinel默认10秒一次向被监视的主服务器发送INFO命令,通过回复获取主服务器的当前信息(包括各个主服务器的slaves).

Sentinel

另外, 服务器选举用的是Raft算法.

3. 集群

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

3.1 节点

连接各个节点的工作通过CLUSTER MEET ip port命令完成,A和B握手后,A会将B的信息通过Gossip协议传播给A所在集群中其他节点,让其他节点也与B握手;

节点

3.2 槽指派

Redis集群通过分片保存键值对,集群的整个数据库分成16384(2^14)个槽,有任何一个槽没被节点处理,则集群处于下线状态.通过向节点发送CLUSTER ADDSLOTS 0 1 2 3 …… 100命令,可以将一个或多个槽指派给节点负责.一个节点除了会将自己负责的槽记录在clusterNode的slots里,还会发送消息告知集群其他节点;

1
2
3
4
5
6
struct clusterNode{
// 用bitmap保存节点的槽
unsigned char slots[16384/8];

int numslots;
};

clusterState中的slots数组记录了集群所有槽的指派信息,而clusterNode.slots数组只记录了一个节点的槽指派;

1
2
3
4
5
6
typedef struct clusterState{
// 记录了每个槽由哪个clusterNode管理
clusterNode *slots[16384];

zskiplist *slots_to_keys;
} clusterState;

节点用键的CRC-16校验和计算槽号, CRC16(key) & 16383;客户端向节点发送命令时,如果键所在槽没有指派给当前节点,返回MOVED错误,指引客户端重定向到正确节点;

集群节点和单机数据库类似,也保存键值对字典和过期字典,节点只能使用0号数据库.节点还会用clusterState中的slots_to_keys跳表来保存槽与键的关系,保存哪个键在哪个槽里,分值是槽号,可以快速获得同一个槽里的若干个键;

3.3 重新分片

Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点.

重新分片可以在线进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求.

3.4 复制和故障转移

集群中可以用CLUSTER REPLICATE node_id 让节点成为node_id的从节点,并开始对主节点复制,并告知集群其他节点;

每个节点定期向其他节点发送PING,如果一个主节点被半数以上主节点标记为下线,则标记为已下线.从它的从节点选举出新的主节点,和领头Sentinel的选举类似,先到先得,超过半数.然后执行SLAVE no one,成为新的主节点,负责处理槽,告知其他节点;

3.5 消息

节点间主要有MEET,PING,PONG,FAIL,PUBLISH五种消息.

3.6 ASK错误和MOVED错误

MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点;
ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施.

4 参考文献

<< Redis设计与实现 >> (黄健宏 著)

《Redis设计与实现》读书笔记——复制、Sentinel、集群

谢谢你请我吃糖果!