主从同步可以用于实现读写分离,减轻服务器读写压力,也可以用来做容灾备份,REDIS实现了这一功能,下面跟大家分享一下,我的学习笔记。
目录
1 概念介绍
用户可以通过slaveof命令或者配置slaveof选项,让一个服务器复制另一个服务器,也叫主从复制。
假设有两台服务器 10.0.2.39:6379 10.0.0.3:6379,如果我们在 10.0.0.3 执行SLAVEOF 命令
那么服务器 10.0.0.3 将会成为 10.0.2.39 的从服务器,而2.39将会成为 0.3的主服务器。主从数据库将保存相同的数据,也叫”数据库状态一致“。
当我们在主服务器执行指令 SET msg "hello world"
那么可以在从服务器上获取msg的值
如果在主服务器上删除了键 msg
那么从服务器上的msg键也会被删除
2 旧版复制功能的实现
复制功能分为同步(sync)和命令传播(command propagete)阶段:
- 同步操作负责将主服务器的数据同步到从服务器
- 命令传播操作负责将主服务器的数据修改传播到从服务器,让恢复主从一致。
2.1 同步
当客户端发送 SLAVEOF 命令到从服务端时,从服务器首先要进行同步操作。
从服务器通过向主服务器发送SYNC指令来完成同步操作,以下时SYNC命令的执行步骤:
- 从服务器向主服务器发送SYNC命令
- 主服务器接收到SYNC命令,执行BGSAVE命令,后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
- 当主服务器执行完BGSAVE命令时,将生成的RDB文件发给从服务器,从服务器接收并载入这个RDB文件,此时从库和主库执行BGSAVE时的数据库状态一致。
- 主服务器将这段时间保存在缓冲区中的写指令发给从服务器,从服务器执行这些写指令,此时主从数据库状态完全一致。
表15-1展示一个主从同步的例子
2.2 命令传播
完成同步操作后,主从服务器数据状态达到完全一致。
但是这种一致是暂时的,主服务器执行写命令后,又会造成主从数据不一致。
为了让主从服务器再次恢复一致,主服务器需要传播执行命令给从服务器,恢复到主从 数据一致的状态。
2.3 旧版复制功能的缺陷
Redis复制可以分为下面2种情况:
- 初次复制:从服务器之前从来没有复制过其他服务器,或从服务器当前要复制的主服务器和上一次的不一致
- 断线后重复制
旧版本重复制功能每次断线后重复制不是同步断线期间主服务器完成的执行命令,而是重新发送SYNC请求,效率极低,表15-2展示了一次旧版断线后重复制的例子
从上表中,我们可以看到从服务器二次接收主服务器的RDB文件不是非必要的,完全可以只同步断线期间,主服务器新添加的k10087 k10088 k10089三个键的数据。但是旧版的断线重复制没有实现这个功能,而是让主从服务器重新执行一次SYNC命令,效率很低。
3 新版复制功能实现
从REDIS 2.8版本开始,使用PSYNC替代SYNC执行同步操作。PSYNC包括完全重同步和部分重同步:
- 完全重同步用于处理初次复制情况,步骤和SYNC一样。
- 部分重同步则用于断线后重复制情况,一定条件下,从服务器断线重连只需要同步断线期间主服务器执行的写命令。
PSYNC解决了旧版断线重复制低效的缺陷。表15-3展示了一次断线重复制的情况。
执行SYNC命令需要生成、传输、载入RDB文件,而部分重同步则只需要执行断线部分指令即可。
3.1 部分重同步的实现
部分同步功能主要由以下三个部分组成:
- 主服务器的复制偏移量和从服务器的复制偏移量
- 主服务器的复制挤压缓冲区
- 服务器的运行ID
3.1.1 复制偏移量
主从服务器分别会维护一个偏移量。
当每次主服务器向从服务器传播N个字节数据,会将自己的偏移量+N,从服务器接收到主服务器传播来的N个字节数据,就将自己的偏移量+N。
下图展示了主服务器向从服务器传播33个字节的数据。
通过对比主从服务器的复制偏移量可以知道主从数据是否一致,如果偏移量一致,那么主从数据一致,否则反之。
3.1.2 复制积压缓冲区
举个例子,主从服务器初始偏移量都是10086,服务器A在接收主服务器传播的数据前离线了,此时服务器A和主服务器产生了数据据不一致。
假设A在离线后立刻重连了,那么主服务器如何补偿断线期间丢失的那部分数据呢?利用复制积压缓冲区。
复制积压缓冲区是一个固定长度的先进先出队列,默认为1M,举个例子,假设该缓冲区的大小为3个字符,那么存储'helloworld'字符串,‘h’,'e','l'先会被放进队列,放满之后最先入队的元素将会被弹出,最后存储的是"r","l","d"。
在主服务器进行命令传播时,不仅将写命令传播给从服务器,还会将命令写入复制积压缓冲区。
因此积压缓冲区会保存一部分最近传播的写命令,并且记录对应的复制偏移量:
当从服务器重连后,从服务器会用PSYNC命令将自己的复制偏移量发给主服务器,主服务器根据偏移量做对应同步操作:
- 当复制偏移量+1仍然存在复制积压缓冲区中,主服务器将对从服务器进行部分同步操作。
- 反之,进行完整同步操作。
复制积压缓冲区大小可以通过repl-backlog-size设置,设置其大小需要注意,可以根据公式 second * write_size_per_second 来估算(断线重连的平均时间*每秒产生的写数据大小),因为设置小了可能不能正常发挥PSYNC的高效作用。
3.1.3 服务器运行ID
除了复制偏移量和复制积压缓冲区外,实现部分同步还用到服务器运行ID(无论是主服务器还是从服务器,每个服务器都有自己的40位运行ID)。
当从服务器第一次链接主服务器时,主服务器会将自己的运行ID发给从服务器保存。从服务器断线重连时,会将当前保存的服务器ID发送给主服务器,主服务器接收判断,如果和自身的ID一致,则进行部分同步,否则执行完整同步操作。
举个例子,假设从服务器A正在复制一个主服务器c574fd06c3b543ba839da718dfe33f54aidb1836,突然断线了,然后重连,从服务器A向主服务器发送c574fd06c3b543ba839da718dfe33f54aidb1836,主服务器判断这串ID和自身是否一致来判断是执行部分同步还是完整同步。
3.2 PSYNC命令的实现
PSYNC命令调用方法有2种:
- 如果从服务器从来没有复制过任何主服务器,或者之前执行过slaveof no one,那么从服务器开始一次新的主从复制时向主服务器发送 PSYNC ? -1
- 否则发送 PSYNC runid offset,其中runid是最后一次保存的主服务器runid,offset是最后一次保存的复制偏移量
主服务器接收到PSYNC命令根据情况回复下面三种之一:
- +FULLRESYNC runid offset:表示接下来进行完整同步操作,并发送自己的运行ID和复制偏移量给从服务器。
- +CONTINUE:表示接下来进行部分同步操作。
- -ERR:表示当前主服务器版本低于2.8,识别失败,从服务器将重新发送SYNC命令给主服务器,进行完整同步
4 复制的实现
通过 slaveof 命令可以实现主从复制,而具体实现细节有以下几个步骤:
- 从服务器设置主服务器的地址
- 从服务器和主服务器建立套接字连接
- 发送PING指令
- 身份验证
- 发送端口信息
- 同步
- 命令传播
4.1 设置主服务器的地址和端口
当客户端向从服务器发送 slaveof 命令的时候,从服务器会将主服务器的 地址 和 端口保存在redisServer结构的masterhost和masterport中:
slaveof 命令是一个异步命令,当设置完masterhost 和 masterport 后,服务器立刻返回 OK,这不表示已经主从复制完成,仅仅表示该请求已被接收,复制工作在服务器返回OK后才开始进行。
4.2 创建套接字连接
执行完SLAVEOF命令后,从服务器会尝试和主服务器建立套接字连接,如果成功,会单独关联一个文件事件处理器用于复制工作,处理RDB文件,传播指令等等。
而主服务器和从服务器建立连接后,会把从服务器当作一个客户端,从服务器可以向主服务器发送请求,主服务器响应回复。
4.3 发送PING命令
当从服务器成为主服务器的客户端后,首先会发送PING给主服务器。
这个PING命令有2个作用:
- 因为建立连接后,从来没有进行过通信,通过PING命令确认套接字读写状态是否正常
- 因为复制工作需要主服务器能正常处理请求的状态下才能进行,通过PING检查主服务器状态
从服务器在发送PING后,可能遇到下面三种情况之一:
- 主服务器返回错误:表示主服务器忙或者其他情况,从服务器此时断开重连。如 BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
- 主服务器正常返回,但从服务器未能在时限内读取出回复内容:说明主从服务器之间网络不佳,不能继续复制工作,从服务器此时断开重连。
- 从服务器读取到”PONG“:表示主从之间网络正常,可以继续执行复制工作。
4.4 身份验证
从服务器接收到主服务器回复的"PONG"后,决定是否进行身份验证。
如果从服务器设置了masterauth,那么从服务器将向主服务器发送一条AUTH命令,命令的参数位masterauth的值。
如果主服务器没有设置requirepass选项,但是从服务器设置了masterauth选项,那么主服务器会返回no password is set错误;
如果主服务器设置了requirepass选项,且与从服务器设置的masterauth一致,那么继续进行下一步复制工作;
如果主服务器设置了requirepass选项,但与从服务器设置的masterauth不一致,那么主服务器会返回一个invalid password错误;
如果主服务器设置了requirepass选项,但从服务器没有设置masterauth选项,那么主服务器会返回一个NOAUTH错误。
4.5 发送端口信息
通过AUTH认证后,从服务器发送 REPLCONF listening-port port-number 告知主服务器自己的端口号。
主服务器接收到这个命令后,会记录在从服务器对应的客户端结构的slave_listening_port中
我们可以通过向主服务器发送 INFO REPLICATION查看主服务器状态:
其中slave0就是一条客户端状态,port 为 6379
4.6 同步
发送端口后,开始正式执行同步任务。从服务器向主服务器发送PSYNC命令,主从达到完全同步状态。
在同步操作之前,从服务器是主服务器的客户端,在这步以后,主服务器也会成为从服务器的客户端,因为无论是传送RDB文件,还是传输命令,主服务器都需要主动向从服务器发送命令,也就是双向通信。
4.7 命令传播
主从进行同步后,就会进入命令传播阶段,这一步主服务器一直会将自己执行的写命令发送给从服务器,保证数据一致。
命令传播阶段,从服务器会每秒一次进行心跳检测 REPLCONF ACK <replication_offset> ,主要作用是:
- 检测主从网络状态
- 辅助实现min-slaves选项
- 检测命令丢失
4.7.1 检测网络状态
主从服务器通过发送和接收 REPLCONF ACK 命令来确认网络连接。
其中slave0一行,lag一栏中可以看到距离从服务器最后向主服务器发送心跳过了多少秒。如果超过1秒,那么说明主从服务器之间出现了网络故障。
4.7.2 辅助实现min-slaves选项
min-slaves-to-write 和 min-slaves-max-lag 两个选项可以防止主服务器在不安全的情况下执行写命令:
- min-slaves-to-write: 最少多少个从服务器
- min-slaves-max-lag: 从服务器的延迟大于等于多少秒
4.7.3 检测命令丢失
由于网络故障,主服务器发送给从服务器的写命令可能半路丢失。
那么当从服务器发送REPLCONF ACK心跳给主服务器时,主服务器会检测偏移量,从复制积压缓冲区中找到从服务器缺失的部分重新发送。
注意:2.8版本之前没有补发数据功能,为了主从数据一致性,最好使用2.8以上的版本。