Redis - 集群,主从复制

1. 为什么需要主从复制?

1)读写分离,单台服务器能支撑的QPS是有上限的,我们可以部署一台主服务器、多台从服务器,主服务器只处理写请求,从服务器通过复制功能同步主服务器数据,只处理读请求,以此提升Redis服务能力;另外我们还可以通过复制功能来让主服务器免于执行持久化操作:只要关闭主服务器的持久化功能,然后由从服务器去执行持久化操作即可。

2)数据容灾,任何服务器都有宕机的可能,我们同样可以通过主从复制功能提升Redis服务的可靠性;由于从服务器与主服务器数据保持同步,一旦主服务器宕机,可以立即将请求切换到从服务器,从而避免Redis服务中断。

2. 主从复制

在Redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制(replicate)另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)。


Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作:

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

2.1 同步

当客户端向从服务器发送SLAVEOF命令,要求从服务器复制主服务器时,从服务器首先需要执行同步操作,将从服务器的数据库状态更新至主服务器当前所处的数据库状态。该过程通过SYNC命令来完成。

过程如下:
在这里插入图片描述
收到SYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。

2.2 命令传播

在同步操作执行完毕之后,主从服务器两者的数据库将达到一致状态,但这种一致并不是一成不变的,每当主服务器执行客户端发送的写命令时,主服务器的数据库就有可能会被修改,并导致主从服务器状态不再一致。

解决途径如下:
主服务器会将自己执行的写命令,也即是造成主从服务器不一致的那条写命令,发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态。

2.3 复制的缺陷

当断线后重复制,处于命令传播阶段的主从服务器因为网络原因而中断了复制,但从服务器通过自动重连接重新连上了主服务器,并继续复制主服务器。这时复制效率就非常的低。而且主服务器如果再次接受一次SYNC命令,就需要再生成RDB文件,其对性能的影响是显而易见的。

2.4 Redis2.8的优化

Redis从2.8版本开始,使用PSYNC命令代替SYNC命令来执行复制时的同步操作。

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partialresynchronization)两种模式:

  • 完整重同步用于处理初次复制情况:完整重同步的执行步骤和SYNC命令的执行步骤基本一样,它们都是通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步。
  • 部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态。

PSYNC命令的部分重同步模式解决了旧版复制功能在处理断线后重复制时出现的低效情况

在这里插入图片描述

2.5 实现原理

执行部分重同步的要求:
1)RUN_ID必须相等;
2)复制偏移量必须包含在复制缓冲区中。

2.5.1 复制偏移量

执行复制的双方——主服务器和从服务器会分别维护一个复制偏移量:

  • 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N。
  • 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N。

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

2.5.2 复制积压缓冲区

复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小为1MB。

当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里面。

主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。因此,主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。


当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作,如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分重同步操作,那么接下来,主服务器向从服务器发送+CONTINUE回复,表示数据同步将以部分重同步模式来进行。进而发送这部分数据给从服务器。

2.5.3 服务器运行ID

每台Redis服务器都有一个运行ID,从服务器每次发送psync请求同步数据时,会携带自己需要同步主服务器的运行ID。
主服务器接收到psync命令时,需要判断命令参数运行ID与自己的运行ID是否相等,只有相等才有可能执行部分重同步。
而当从服务器首次请求主服务器同步数据时,从服务器显然是不知道主服务器的运行ID,此时运行ID以“?”填充,同时复制偏移量初始化为-1。

这样判断的目的是为了查看从服务器保存的运行ID和当前连接的主服务器的运行ID是否相同,可能会出现哨兵类集群管理的主节点的重新选举的情况,那么,如果不同就执行完整重同步操作。

2.5.4 执行流程如下

在这里插入图片描述

2.5.5 复制的实现

通过向从服务器发送SLAVEOF命令,我们可以让一个从服务器去复制一个主服务器,其主要步骤如下:

  1. 设置主服务器的地址和端口
  2. 建立套接字连接,创建连向主服务器的套接字连接
  3. 发送PING命令,检查套接字的读写状态是否正常,以及检查主服务器能否正常处理命令请求
  4. 身份验证,验证密码masterauth是否相同
  5. 发送端口信息,向主服务器发送从服务器的监听端口号
  6. 同步,从服务器将向主服务器发送PSYNC命令,执行同步操作
  7. 命令传播,主服务器只要一直将自己执行的写命令发送给从服务器,而从服务器只要一直接收并执行主服务器发来的写命令,就可以保证主从服务器一直保持一致了

2.6 心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:

replconf ack <replication_offset>

其中replication_offset是从服务器当前的复制偏移量。

发送REPLCONF ACK命令对于主从服务器有三个作用:

  • 检测主从服务器的网络连接状态。
  • 辅助实现min-slaves选项。
  • 检测命令丢失。

辅助实现min-slaves配置选项:
Redis的min-slaves-to-write和min-slaves-max-lag两个选项可以防止主服务器在不安全的情况下执行写命令。当从服务器数量和延迟值就不满足配置条件,或者小于或等于的情况的时候,可以执行拒绝写命令。


检测命令丢失:

如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发送REPLCONF ACK命令时,主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量,然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数据,并将这些数据重新发送给从服务器。

3. 集群

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

3.1 槽

客户端可以请求任意一个节点,每个节点中都会保存所有16384个slot对应到哪一个节点的信息。如果一个key所属的slot正好由被请求的节点提供服务,则直接处理并返回结果,否则返回MOVED重定向信息,发送到该槽所属的结点处理。


当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。

clusterNode结构的slots属性和numslot属性记录了节点负责处理哪些槽:

struct clusterNode{
	unsigned char slots[16384/8];
	int numslots;
}

Redis以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i上的二进制位的值来判断节点是否负责处理槽i。

一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。

3.2 计算槽

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:

  • 如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。
  • 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。

Redis将键空间分为了16384个slot,通过算法计算出每个key所属的slot。

HASH_SLOT = CRC16(key) mod 16384 

可以使用CLUSTER KEYSLOT<key>命令可以查看一个给定键属于哪个槽。

实际应用中,Redis客户端可以通过向集群请求slot和节点的映射关系并缓存,然后通过本地计算要操作的key所属的slot,查询映射关系,直接向正确的节点发起请求,这样可以获得几乎等价于单节点部署的性能。


当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端转向至正在负责槽的节点。

当客户端接收到节点返回的MOVED错误时,客户端会根据MOVED错误中提供的IP地址和端口号,转向至负责处理槽slot的节点,并向该节点重新发送之前想要执行的命令。

当集群由于节点故障或者扩容导致重新分片后,客户端先通过重定向获取到数据,每次发生重定向后,客户端可以将新的映射关系进行缓存,下次仍然可以直接向正确的节点发起请求。

Redis集群中,客户端只能在主节点执行读写操作。如果需要在从节点中进行读操作,需要满足如下条件:① 首先在客户端中执行readonly命令;② 如果一个key所属的slot由主节点A提供服务,则请求该key时可以向A所属的从节点发起读请求。该请求不会被重定向。

3.3 重新分片

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

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

其流程如下:
在这里插入图片描述


当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:

  • 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
  • 相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。

和接到MOVED错误时的情况类似,集群模式的redis-cli在接到ASK错误时也不会打印错误,而是自动根据错误提供的IP地址和端口进行转向动作。如果想看到节点发送的ASK错误的话,可以使用单机模式的redis-cli客户端。

节点会检查自己的clusterState.migrating_slots_to[i]来查看槽i是否正在进行迁移。

ASK错误和MOVED错误的区别

  • MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。
  • ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。

流程如下:
在这里插入图片描述

3.4 主从替换

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。

Redis集群中节点的故障状态有两种。一种为pfail(Possible failure),当一个节点A未在指定时间收到另一个节点B对ping包的响应时,A节点会将B节点标记为pfail。另一种是,当大多数Master节点确认B为pfail之后,就会将B标记为fail。fail状态的节点才会需要执行主从切换。

当集群中节点通过错误检测机制发现某个节点处于fail状态时,会自动执行主从切换。

Redis中还提供一种手动执行切换的方法,即通过执行cluster failover命令。

自动切换

集群之间会互相发送心跳包,心跳包中会包括从发送方视角所记录的关于其他节点的状态信息。当一个节点收到心跳包之后,如果检测到发送方(假设为A)标记某个节点(假设为B)处于pfail状态,则接收节点(假设为C)会检测B是否已经被大多数主节点标记为pfail状态。如果是,则C节点会向集群中所有节点发送一个fail包,通知其他节点B已经处于fail状态。

当一个主节点(假设为B)被标记为fail状态后,该主节点的所有Slave执行周期性函数clusterCron时,会从所有的Slave中选择一个复制偏移量最大的Slave节点(即数据最新的从节点,假设为D),然后D节点首先将其当前纪元(currentEpoch)加1,然后向所有的主节点发送failover授权请求包,当获得大多数主节点的授权后,开始执行主从切换。

流程如下,假设被切换的主节点为M,执行切换的从节点为S:

1)S先更新自己的状态,将自己声明为主节点。并且将S从M中移除。
2)由于S需要切换为主节点,所以将S的同步数据相关信息清除(即不再从M同步数据)。
3)将M提供服务的slot都声明到S中。
4)发送一个PONG包,通知集群中其他节点更新状态。

手动切换

当一个从节点接收到cluster failover命令之后,执行手动切换,流程如下:
1)该从节点首先向对应的主节点通知主节点从节点要开始进行手动切换。
2)主节点会阻塞所有客户端命令的执行。之后主节点在周期性函数clusterCron中发送ping包时会在包头部分做特殊标记。
3)当从节点收到主节点的ping包并且检测到特殊标记之后,会从包头中获取主节点的复制偏移量。
4)从节点在周期性函数clusterCron中检测当前处理的偏移量与主节点复制偏移量是否相等,当相等时开始执行切换流程。
5)切换完成后,主节点会将阻塞的所有客户端命令通过发送+MOVED指令重定向到新的主节点。通过该过程可以看到,手动执行主从切换时不会丢失任何数据,也不会丢失任何执行命令,只在切换过程中会有暂时的停顿

选举新的主节点(先到先得)(Raft算法的领头选举)

集群选举新的主节点流程:
1)集群的配置纪元是一个自增计数器,它的初始值为0。

2)当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一。

3)对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。

4)当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。

5)如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。

6)每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持。

7)如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张支持票时,这个从节点就会当选为新的主节点。

8)因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个。

9)如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

3.5 消息分类

一条消息由消息头(header)和消息正文(data)组成。

节点发送的消息主要有以下五种:

  • MEET消息:当发送者接到客户端发送的CLUSTER MEET命令时,发送者会向接收者发送MEET消息,请求接收者加入到发送者当前所处的集群里面。
  • PING消息:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中的节点是否在线。
  • PONG消息:当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于这个节点的认识。
  • FAIL消息:当一个主节点A判断另一个主节点B已经进入FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将节点B标记为已下线。
  • PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。

读书笔记 - 《Redis 设计与实现》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值