redis 复制
2.8版本前后复制原理有所不同,2.8之前称为旧版,2.8后称为新版。
旧版复制实现原理
redis的复制操作分为同步(sync)和命令传播(command propagate) 两个操作:
- 同步操作用于将从服务器状态更新为主服务器当前所处的服务器状态
- 命令传播操作则用于当主服务器状态被修改,导致主从状态不一致时,让主从状态恢复到一致状态。
同步
当客户端向服务器发送slaveof
命令,要求从服务器复制主服务器时,从服务器首先要执行同步操作。同步操作通过向主服务器发送sync
命令实现:
- 从向主发送
sync
命令 - 收到sync命令的主服务器执行
bgsave
,在后台生成一个RDB文件,并使用一个缓冲区记录现在开始所有执行的写命令 - 主服务器将生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,并将状态更新至主服务器执行bgsave之前的状态
- 主服务器将缓冲区记录的写命令发送给从服务器,从服务器接收并执行这些命令,将数据库状态更新为主服务器当前的数据库状态
表15-1 为同步例子:
命令传播
当同步操作完成后,主从状态达到一致,但这个状态不是一成不变的,主服务器执行写命令后,状态也可能发生变化。
所以需要命令传播操作:当主服务器执行完写操作后,也会将该命令发送给从服务器执行,来保证主从状态一致。
旧版同步缺陷
redis中复制有两种情况:
- 初次复制:从服务器第一次复制,或者和上一次复制的主服务器不同
- 断线后重复制:处于命令传播阶段的主从服务器因为网络原因中断了复制,但从服务器中断重连,继续复制。
问题是什么呢?问题是重连后从服务器还会发送sync命令,主服务器执行bgsave命令再生成一个新的RDB文件,这个RDB是包含断线之前的数据的,而且sync命令是很耗资源的,为了同步一小部分缺失的数据,却让从服务器执行一次sync命令,f这个方式是很低效的。
sync是个很耗费资源的操作:
- 主服务器需要执行bgsave命令来生成RDB文件,这个操作会耗费主服务器的CPU、内存和磁盘IO资源;
- 主服务器要将RDB文件发送给从服务器,这个操作耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应请求的时间有影响;
- 接收RDB文件的从服务器需要载入RDB文件,在载入期间,从服务器会因为阻塞而无法处理命令请求。
因为sync非常耗费资源,所以redis保证真有必要时才执行sync
新版复制功能
为了解决旧版断线重连情况下的低效问题,2.8版本开始使用psync
命令来实现复制的同步操作:
psync有完整重同步和部分重同步两种模式:
- 完整重同步:用于初次复制:和sync命令基本一样
- 部分重同步:用于断线重连情况:当从服务器重连后,如果条件允许,主服务器将从服务器断开期间执行的写命令发送给从服务器。
表15-3展示了psync如何高效的处理断线重连场景下的同步:
对比sync处理断线重连情况下不难发现,虽然都可以保证状态一致,但是执行部分重同步所需的资源比sync所需的资源要少得多,速度也更快。
部分重同步通信过程如下:
部分重同步的实现
部分重同步有三部分构成:
- 主从服务器的复制偏移量(replication offset);
- 主服务器的复制积压缓冲区(replication backlog);
- 服务器的运行ID(run id)
复制偏移量
- 主服务器每次向从服务器传播N个字节的数据时,就对偏移量加N
- 从服务器收到N个节点的数据后,也对偏移量加N
通过对比偏移量判断主从状态是否一致。
但是只要偏移量还是不能确定要恢复哪些数据,还需要复制积压缓冲区。
复制积压缓冲区
是由主服务器维护的一个固定长度的先进先出(FIFO)队列,默认大小为1MB。
如果待入队大小大于队列大小,那么会弹出队首,继续入队。
当主服务器进行命令传播时,不仅将写命令发送给从服务器,还会将写命令入队到复制积压缓冲区:
当从服务器重连后通过psync命令将自己的偏移量发送给主服务器,主服务器根据这个偏移量觉得使用哪种同步操作:
- 如果offset+1开始的数据还在复制积压缓冲区内,那么将执行部分重同步操作;
- 相反,如果不在复制积压缓冲区内,将执行完整重同步操作。
复制积压缓冲区大小公式:重连时间 * 每秒产生的写命令大小
为了安全起见,可以在此基础上乘以2,这样可以保证绝大部分断线情况可以使用部分完整性来同步。
服务器ID
除了复制偏移量和复制积压缓冲区外,部分重同步还需要用到服务器运行ID:
- 每个服务器都有自己的运行id
- 运行ID在服务器启动时自动生成,由40个随机的16进制字符组成
当初次复制时,主服务器会将自己的运行ID发送给从服务器,从服务器保存起来。
当从服务器断线重连后,从服务器向当前连接的主服务器发送之前保存的运行ID:
- 如果相同,说明就是第一次复制时的主服务器,主服务器尝试部分重同步操作;
- 如果不同,说明不是第一次复制时的主服务器,主服务器将执行完整重同步操作。
psync命令的实现
复制积压缓冲区是实现部分重同步的方式,偏移量和运行ID将决定主服务器使用完整重同步还是部分重同步。
- 如果从服务器之前没有复制过任何主服务器,那么初次复制将发送
psync ? -1
命令,主动请求主服务器执行完整重同步操作(因为初次复制不能部分重同步) - 如果已经复制过,那么将向主服务器发送:
psync <runid> <offset>
命令,主服务器将判断runid和offset来决定使用哪种复制方式。
收到psync命令的主服务器将回复三种回复的一种:
- 如果返回 +fullresync <runid> <offset>,表示主服务器执行完整重同步,runid为主服务器的运行id,从服务器下一次发送psync使用;offset为复制偏移量,从服务器将这个值最为初始化偏移量。
- 如果返回 +continue ,表示主服务器将执行部分重同步,从服务器等待同步就好。
- 如果返回 -ERR, 表示主服务器低于2.8版本,识别不了psync命令,从服务器将发送sync命令,进行完整性同步。
复制步骤
- 设置主服务器的地址和端口,通过slaveof ip port 命令
- 建立套接字连接
- 发送ping命令
- 身份验证
- 发送端口信息
- 同步
- 命令传播
心跳检测
在命令传播阶段,从服务器每秒1次向主服务器发送命令:
replconf ack <replication_offset> 复制偏移量
心跳检测的作用:
- 检测主从服务器的网络连接状态
- 辅助实现min-slaves选项
- 检测命令丢失
回顾
- redis 2.8 之前不能高效解决断线重连后的复制问题,2.8新增的部分重同步解决了
- 部分重同步通过复制偏移量、复制积压缓冲区、服务器运行ID三部分构成
- 复制分为同步和命令传播两部分,同步通过从服务器发送命令实现,命令传播
- 主服务器通过命令传播来更新从服务器状态,保持主从数据一致;而从服务器向主服务器发送命令来进行心跳检测,以及命令丢失检测。