复制
-
用户可以设置
SLAVEOF选项来使一个服务器去复制另外一个服务器,称被复制的服务器叫做主服务器,对主服务器复制的服务器叫做从服务器。 -
进行复制的主从服务器双方需要使双方数据库保存同样的数据,称为
一致。
例如,主服务器执行如下命令:SET MSG "VMS" ,那么这个时候,从机也需要执行相同的命令来保证数据的一致。
如果一个服务器成为另一个服务器的从机,那么
- 主机负责写,从机只能负责读;
- 主机所有的数据,从机会自动保存
对端口号为6380的服务器执行slaveof命令,使其成为端口号为6379服务器的从机:
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # 使自己作为6379的从机
这个时候,如果主机6579执行写操作
则可以在从机进行读取数据:
注意,从机是不能写数据的,如果执行写数据,报错:
复制功能的实现
Redis复制功能分为两步:
- 同步: 作用是服务器的状态更新至主服务器的状态
- 传播: 作用是同步之后,主从服务器状态是一致的,如果这个时候主服务器执行写操作使自己的状态改变,则导致主从状态的不一致,传播会将主服务器刚才执行的写操作命令传播到从服务器,使主从服务器数据库回到一致的状态.
同步
同步发生的时间
当服务器执行SLAVEOF命令,要求从服务器复制主服务器的时候,从服务器先执行同步操作.
目的是将从服务器的状态更新为当前主服务器所处的状态.
具体步骤
- 从服务器向主服务器发送
PSYNC命令 - 收到命令的主服务器执行
BGSAVE操作(不懂这个命令的可以参考juejin.cn/post/692684…),在后台生成一个RDB文件,并使用一个缓冲区来记录开始执行BGSAVE之后的所以写命令. - 当主服务器完成
BGSAVE命令以后,主服务器将生成的RDB文件发送给从服务器,从服务器接受到之后载入这个RDB文件,将自己的数据库状态设置为主服务器执行BGSAVE命令时候的状态. - 主服务器将第二步设置的缓冲区的所有写命令发送给从服务器,从服务器执行这些命令(这个时候从服务器是阻塞执行的,载入RDB文件的时候,不能执行其余的命令),从服务器执行完毕,主从服务器的状态就一致了.
传播
传播发生的时间
同步完毕以后,主从服务器的状态是一致的,这个时候,如果主服务器执行了写命令,那么主服务器状态会改变,主从服务器状态会不一致.
为了让主从服务器再次到达状态一直的情形,主服务器需要对从服务器执行命令传播的操作:
- 主服务器将自己刚才执行的写命令发送给从服务器
- 从服务器收到之后执行这个命令
- 命令执行以后,主从服务器状态再次一致
完全重同步 以及 部分重同步
- 完全重同步用于初次复制的时候,即上文的同步阶段,通过让主服务器发送RDB文件以及主服务器中的缓冲区的写命令来进行同步.
- 部分重同步用于从从服务器掉线后重新连接主服务器的时候使用.在从服务器掉线的时候,主服务器可能会进行一些写操作,当从服务器上线后,如果条件允许, 主服务器将从服务器连接断开后发生的写命令发送给从从服务器,从服务器只要执行了这些写命令,主从服务器的状态就保持一致了.
部分重同步的实现
部分重同步由以下三个部分组成:
- 主服务器的复制偏移量 和 从服务器的复制偏移量
- 主服务器的复制积压缓冲区
- 服务器的运行ID
复制偏移量
主从服务器双方都会维护一个复制偏移量
- 每当主服务器向从服务器传播N个字节的数据,就讲自己的复制偏移量值加上N
- 每当从服务器接受到主服务器发送来的N个字节的数据,就讲自己的复制偏移量加上N.
可知:
- 如果主从服务器状态一致,那么双方的复制偏移量总是相同的
- 如果双方复制偏移量不同,那么二者状态一定不一致
如果当前主服务器A的复制偏移量是1000,从服务器B的复制偏移量是950,此时B掉线,当B重新连接上A的时候,B向A发送PSYNC命令报告自己的复制偏移量是950,如果此时A中依然保存着950以后执行的命令,就可以向B发送从950位置以后的写操作,B执行后就可以同步. 如果这个时候A的950以后的命令数据已经没有了呢?
复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度的先进先出队列,默认大小是1MB.
当主服务器进行命令传播的时候,它不仅会将自己执行的写命令发送给所有从服务器,而且会将写命令入队到复制积压缓冲区.如图:
这个时候, 主服务器的复制积压缓冲区会保存一部分最近传播的写命令,复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量.
从服务器重连上主服务器的时候,发送PSYNC命令的时候,会将自己的复制偏移量offset发送给主服务器,主服务器根据这个偏移量来决定执行什么操作:
- 如果offset后面的数据(offset+1开始的数据)依然存在于复制积压缓冲区,则只需要执行部分复制操作.
- 如果数据已经没有了,则需要执行完全重同步操作.
服务器运行ID
- 每个服务器,不论主从,都会有自己的运行ID.
- 运行ID自动生成,是一组40个随机十六进制数组成的.
从服务器进行初次复制的时候,主服务器将自己的运行ID发送给从服务器,从服务器会保存这个ID. 从服务器重新连上主服务器的时候,从服务器向当前连接的主服务器发送之前保存的运行时ID:
- 如果主服务器当前ID与从服务器发来的ID相同,则说明连接的是同一个主服务器,主服务器尝试进行部分重同步操作.
- 如果不同,说明本次的主服务器和之前的主服务器不是同一个,那么执行完全重同步.
PSYNC命令的实现
命令调用时机
-
如果从服务器没有复制过任何的主服务器,或者之前执行了
SLAVEOF NO ONE命令,那么从服务器开始一次新的复制的时候,将向主服务器发送PSYNC ? -1,主动请求进行完整重同步. -
如果之前从服务器已经复制过主服务器,那么从服务器开始一次新的复制操作的时候,将发送
PSYNC <runid> <offset>:runid代表上一次复制时候的主服务器id,offset代表从服务器当前的复制偏移量,主服务器根据这两个参数来决定执行那种操作:
- 如果主服务器返回
+FULLRESYNC <runid> <offset>,则说明主从服务器将执行完全重同步.runid是主服务器的运行id,从服务器进行保存;offset是主服务器的当前复制偏移量,从服务器将这个值作为自己的初始化偏移量. - 如果返回
+CONTINUE,说明将执行部分重同步操作,从服务器只要执行主服务器发送过来的自己缺少的那一部分即可. - 如果返回
-ERR,说明有版本问题.
复制的完整步骤
设置主服务器的地址和端口
-
从服务器执行
SLAVEOF ip port的时候,便知道了主服务器的ip地址和端口号,例如SLAVEOF 127.0.0.1 6379,就把ip地址保存自己的到masterhost属性,端口号保存到masterport属性. -
SLAVEOF是一个异步命令,在完成设置masterhost属性和masterport属性之后,从服务器就返回OK给命令行,表示复制命令已经被接收,但是实际的复制工作在返回OK之后才会进行.
建立套接字连接
执行SLAVEOF指令以后,从服务器根据masterhost属性和masterport属性,创建向主服务器的套接字连接.
- 如果从服务器创建的套接字成功连接到主服务器,那么从服务器创建一个用于复制工作的文件事件处理器,这个处理器将负责执行后序的复制工作,比如接受RDB文件以及主服务器的发来的传播命令等.
- 主服务器接受从服务器的套接字连接一后,将为该套接字创建相应的客户端状态,把从服务器看做一个客户端来看待,此时:从服务器既是(主服务器的)客户端,又是(连向自己的redis-cli的)服务器.从服务器可以向主服务器发送命令,主服务器向从服务器发命令回复.
发送PING
从服务器成为主服务器的客户端之后,向主服务器发送PING,作用如下:
- 两者之前从未进行通信,PING可检查套接字的读写状态是否正常
- 检查主服务器是否可以正常处理请求
身份验证
如果从服务器设置了masterauth,则进行身份验证.
如果从服务器没设置masterauth,则进行身份验证.
- 主服务器设置
requirepass,从服务器设置了masterauth:密码正确则继续下一步;否则返回错误 - 主服务器没设置
requirepass,从服务器设置了masterauth:返回错误 - 主服务器设置
requirepass,从服务器设没置了masterauth:返回错误 - 主服务器没设置
requirepass,从服务器没设置了masterauth:继续下一步
发送端口信息
身份验证之后,从服务器向主服务器发送从服务器的端口号.
主服务器接受到之后,将端口号记录下来,以方便info replication的时候,显示自己拥有的从服务器的端口号.
同步
从服务器发送PSYNC命令,执行同步操作,并将自己的数据库更新到主服务器当前所处的状态.
执行完毕以后,主服务器也成为了从服务器的客户端.
此时,主从服务器都是对方的客户端,可以相互发送命令请求,或者相互返回命令请求.
命令传播
同步以后,主从服务器进入命令传播阶段,此时主服务器只要一直将自己执行的写命令发送给从服务器,从服务器执行完主服务器发来的写命令,就可以一直保证主从服务器是一致的了.
心跳检测
在命令传播阶段,从服务器默认每一秒向主服务器发送命令,来:
- 检测主从服务器的网络连接状态
- 辅助实现min-slaves选项
- 检测命令丢失
检测主从服务器的网络连接状态
如果主服务器一秒钟没有收到从服务器发来的心跳命令,那么主服务器就知道主从服务器之间连接出现了问题.
辅助实现min-slaves选项
Redis有min-slaves-to-write和min-slaves-max-lag两个选项可以防止主服务器在不安全的情况下执行写命令.
- 如果
min-slaves-to-write= 3 - 如果
min-slaves-max-lag= 10 那么:在服务器从机少于3个,或者三个从服务器的网络延迟都>=10秒的时候,主服务器拒绝执行写命令.
检测命令丢失
如果主服务器发送给从服务器的写命令丢失,当从服务器发送心跳信息的时候,则主服务器检测到从服务器的复制偏移量少于自己的复制偏移量,然后主服务器根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到服务器缺少的数据,并将这些数据重新发送给从服务器.
注意:这个操作与部分重同步十分的类似,两者的区别在于:本操作是在命令主从服务器没有掉线的时候进行的;部分重同步是在主从服务器掉线重连之后执行的.
参考
Redis设计与实现