一、复制
1.1旧版功能的实现
旧版Redis的复制功能分为 同步(sync)和 命令传播。
- 同步用于将从服务器更新至主服务器的当前状态。
- 命令传播用于 主服务器状态变化时,让主从服务器状态回归一致。
1.1.1同步
当客户端向服务端发送slaveof命令,要求从服务器复制主服务器时,首先从服务器需要进行同步操作。
同步操作需要 从服务器向主服务器发送 SYNC命令 来完成,以下是SYNC命令的执行步骤。
- 从服务器向主服务器发送 SYNC命令
- 收到SYNC命令后,主服务器执行BGSAVE命令,在后台生成RDB命令,并且使用一个 缓冲区 记录从现在开始执行的所以写命令。
- BGSAVE命令执行完毕是,主服务器讲生成的RDB文件发送给从服务器,从服务器接收并载入。
- 主服务器将记录在缓冲区的所有写命令发送给从服务器,从服务器执行这些命令。最终达到同步效果。
注意:在开始执行BGSAVE命令后,记录在缓冲区里的写命令并不会记录在RDB文件中。RDB中记录的是开始BGSAVE命令之前的数据,从以下例子也能看出。
1.1.2命令传播
在同步操作之后,当主服务器执行客户端发送的写命令时,主服务器的数据库可能被修改,导致主从服务器状态不一致。为了使主从的状态回归一致,主服务器需要执行 命令传播 操作。
主 会将自己执行的写命令也即造成状态不一致的命令,发送给从服务器,当从服务器执行相同写命令后,主从状态回归一致。
1.2旧版复制功能的缺陷
1.2.1从服务器对主服务器的复制的两种情况
- 初次复制:从服务器以前没有复制过任何服务器,或者当前要复制的服务器和上次复制的主服务器不同。
- 断线后重复制。
1.2.2 缺陷
对于初次复制的情况,旧版能很好的解决,但是对于断线后重复制,效率就非常低了。
旧版实现断线后重复制的方法是,断线重连后,又执行一遍sync命令。
但是实际上我们仅仅需要同步 断线期间主服务器执行过的写操作。而sync命令是非常耗费资源的操作!
1.3新版复制功能的实现
从redis 2.8版本开始,使用 PSYNC命令 代替SYNC命令来执行复制时的同步操作。
PSYNC命令具有 完整重同步 和 部分重同步 操作。
完整重同步用于初次复制的情况,和sync命令基本一样。
部分重同步用于断线后重复制的情况。当从服务器断线后重连,如果条件允许,主服务器将 断线期间执行的写命令发送给从服务器,从服务器接收并执行即可。
1.4部分重同步的实现
部分重同步功能由以下三个部分构成:
- 主服务器和从服务器的 复制偏移量(replication offset) ;
- 主服务器的 复制积压缓冲区(replication backlog)
- 服务器的运行ID (run ID)
1.4.1复制偏移量
主从服务器都会维护一个复制偏移量:
- 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量加上N。
- 从服务器每次接收到主服务器的N个字节后,就将自己的复制偏移量加上N。
对比主从服务器的复制偏移量,相同则状态一致,不同则不一致。
1.4.2复制积压缓冲区
复制积压缓冲区是主服务器维护的一个固定长度的 先进先出 队列,默认大小为1MB。
当主服务器进行命令传播时,会将写命令写入复制积压缓冲区。
主服务器的复制积压缓冲区里面保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的 复制偏移量。
当从服务器重连时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器:
- 如果offset偏移量之后的数据还在复制积压缓冲区,则进行部分重同步。
- 否则,将执行完整重同步。
1.5PSYNC命令的实现细节
PSYNC命令的调用方法有两种
根据情况,主服务器将会有以下三种的某种回复:
- 主服务器返回 +FULLERSYNC ,表示执行完整重同步。runid和offset是主服务器的,从服务器将会以offset作为自己的初始化偏移量。
- 返回 +CONTINUE,表示执行部分重同步。
- -ERR,表示主服务器的版本低于2.8,识别不了PSYNC,将执行SYNC命令。
1.6复制的实现
通过向服务器发送slaveof命令,可以让一个服务器去复制另一个服务器。
slaveof <master_ip> <master_port>
1.6.1步骤1:设置主服务器的地址和端口
从服务器将客户端给定的 主服务器IP及端口绑定到服务器的相关属性中。
slaveof命令是一个异步过程,在完成属性设置后,向发送slaveof命令的客户端返回OK。表示复制指令接收,而实际的复制工作将在O返回后才开始执行。
1.6.2步骤2:建立套接字连接
设置好IP及端口属性后,从服务器将会创建连向主服务器的套接字连接。
如果从服务器创建套接字能成功连接到主服务器,那么从服务器将会为其关联一个专门用于处理复制工作的文件事件处理器,负责后续的复制工作,比如接收RDB文件,接收主服务器传来的写命令等。
主服务器在接收(accept)从服务器的套接字连接后,将为该套接字创建相应的客户端状台3,并将从服务器看作一个连接到主服务器的客户端对待。
1.6.3步骤3,发送PING命令
从服务器成为主服务器的客户端之后,做的第一件事就是向主服务器发送 一个 PING命令。
PING命令的作用:
- 检查套接字读写状态是否正常;
- 检查主服务器能否正常处理请求命令。
发送PING命令后将会遇见以下三种情况的一种:
- 主服务器返回一个命令回复,但是从服务器却不能在规定时间读取出命令回复的内容,表示网络连接状态不佳,从服务器断开并重建 套接字。
- 主服务器回复一个错误,表示主服务器暂时没办法处理从服务器的命令请求。从服务器断开并重建 套接字。
- 从服务器读取到 “PONG”,流程继续正常进行。
1.6.4步骤4:身份验证
从服务器接收到 PONG 后下一步就是决定是否需要身份验证:
以下由流程图来说明这一过程。
1.6.5步骤5:发送端口信息
身份验证后,从服务器向主服务器发送从服务器的监听端口号。主服务器将其记录在从服务器所对应的客户端状态属性中。
1.6.6步骤6:同步
在这一步,从服务器向主服务器发送PYSNC命令,执行同步操作。
值得一提的是,在同步操作执行之前,只有从服务器是主服务器的客户端,同步操作之后,主服务器成为从服务器的客户端。
1.6.7步骤7:命令传播
完成同步操作之后,主服务器就会进入命令传播阶段,只要主服务器一直将执行的写命令发送给从服务器就可以了。
1.7心跳检测
在命令传播阶段,从服务器默认以每秒一次的频率向主服务器发送命令。
REPLCONF ACK <replication_offset>
其中replication_offset是从服务器当前复制偏移量。
该命令注意有三个作用:
- 检测主从服务器的网络连接状态
- 辅助实现min-slaves选项
- 检测命令丢失。
1.7.1检测主从服务器的网络连接状态
通过发送和接收 REPLCONF ACK 命令来检测网络连接是否正常。
如果主服务器超过一秒没有接收到该命令,主服务器就知道它们之间的连接出现问题了。
通过向主服务器发送 INFO replication命令,在从服务器列表的lag一栏可以看到相应从服务器最后一次向主服务器发送REPLCONF ACK 命令距离现在过了多少秒。
1.7.2辅助实现min-slaves配置选项
redis的 min-slaves-to-write 和 min-slaves-max-lag两个选项可以保证防止主服务器在不安全的情况下执行写命令。
前者表示 最小从服务器数量,后者为 最小服务器数量的延迟值需要小于多少秒。
min-slaves-to-write 3
min-slaves-max-lag 10
如果不符合,则主服务器拒绝执行写命令。
1.7.3检测命令丢失
如果复制偏移量对应不上,那么主服务器将会将复制积压缓冲区里的数据发送给从服务器,以维持状态一致。
注:本文为个人学习笔记,文章内容摘自黄健宏《Redis设计与实现》。redis的复制功能是sentinel和集群的基础之一,本文简单概括了redis复制功能的设计与实现,如果想详细了解过程建议看一下Redis设计与实现这本书,强列推荐!
大家有什么疑问可以评论或者私信,一起进步。
多机功能后面会持续更新。