前言
关系数据库通常会使用一个主服务器向多个从服务器发送更新,并使用从服务器来处理所有的读请求,Redis采用了同样方法来实现自己的复制特性。
简单总结起来就是:在接收到主服务器发送的数据初始副本之后,客户端每次主服务器进行写命令时,从服务器都会实时地得到更新。部署好主从服务器之后,客户端可以向任意的从服务器发送读请求。
本文主要介绍Redis实现数据同步复制简单过程、新旧版本之间的对比、复制的实现、以及一些需要注意的细节,但不会涉及Redis源码解释。只对基本流程及功能进行详细描述。
主要参考资料《Redis实战》与《Redis设计与实现》(有需要的同学可以私信或者评论)
一、旧版复制功能
Redis 2.8以前采用的复制都为旧版复制,主要使用SYNC命令同步复制,SYNC存在很大的缺陷严重消耗主服务器的资源以及大量的网络连接资源。Redis 2.8之后采用PSYNC命令替代SYNC,解决完善这些缺陷,但在介绍新版复制功能之前,必须先介绍旧版复制过程,这样才能更好地形成对比。
1、复制功能的两种模式
同时复制过程分为同步sync与命令传播(command propagate),两个过程配合执行才能实现Redis复制。
1)同步操作:
通过从服务器发送到SYNC命令给主服务器-------->主服务器生成RDB文件并发送给从服务器,同时发送保存所有写命令给从服务器------>从服务器清空之前数据并执行解释RDB文件------->保持数据一致(还需要命令传播过程才能保持一致)
2)命令传播操作:
主服务器的数据库状态被修改(主服务器执行写命令,修改数据库),导致主从服务器数据库不一致时,通过发送让主从服务器不一致的命令(主服务器接收到的新写命令)给从服务器并执行,让主从服务器的数据库重新回到一致状态。
比如初次同步完成后,主从服务器数据库中都已经存在k1-k5的键,处于数据一致的状态
之后,主服务器客户端发送DEL删除命令,删除k3键,导致主从服务器数据不一致
为了让主从服务器数据再次回到一致状态,主服务器向从服务器发送DEL命令,紧接着从服务器接收并执行。即可回到一致状态
2、配置选项前提
1)主从复制的前提不用多说,就是先正确配置redis主从服务器,主要通过slaveof ip port选项配置或者SLAVEOF 命令。
2)保证主服务器的RDB+AOF配置正确,特别是RDB中dbfilename选项与AOF中的dir选项,两个文件路径对于Redis是可写的
3、主从复制过程
文字简单总结描述:
1)slave会建立和master的连接,然后发送sync命令;
2)master都会启动一个后台进程执行BGSAVE命令,将数据快快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来;、
3)后台进程完成写文件后,master发送文件给slave,slave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上。
4)紧接着master就会把缓存命令转发给slave,后续的master收到的写命令也通过跟slave连接发送给slave;
5)如果master同时接收到多个slave发来的同步连接请求,只会启动一个进程来写数据库镜像,然后发送给所有slave。
也可以参考以下表,其中步骤1-4可以认为是sync同步操作,而步骤5即为命令传播模式
注意事项
1)从服务器在同步时,会清空所有数据,服务器在与主服务器进行初连接时,数据库中的所有数据都将丢失,替换成主服务器发送的数据。
2)Redis不支持主主复制
3)主从复制不会阻塞master(不会阻塞master处理客户端请求),相反slave在初次同步数据时会阻塞不能处理客户端请求。
4)当多个从服务器尝试连接同一个主服务器的时候,就会出现以下两种情况:
一是:步骤3还未执行,所有从服务器都会接收到相同的快照文件和相同缓冲区写命令。
二是:步骤3正在执行或者已经执行完毕,当主服务器与较早的从服务器完成以上全部步骤之后,主服务器会新连接的从服务器重新依次执行1-5步骤。
在大部分情况下,Redis会尽可能去减少复制所需要的工作,但是从服务器连接的时机不凑巧的话,只好多做一些外额外工作。
5)多个从服务器连接主服务器时候,同步数据可能会占用很大一部分的带宽,可能会导致其他请求难以到达主服务器。
4、SYNC命令的缺陷
主要是主从服务器断线后重复制,即处于命令传播阶段的主从服务器由于网络断开,从服务器一直尝试连接主服务器连接成功后,继续复制主服务器。如下过程在主从服务器断开后重新连接期间,主服务器继续执行三个SET命令,导致从服务器连接后发送L SYNC命令,重新进行了“全量”复制过程,RDB文件中包含k1-k10089全部的键。
其中可以明显看出重新连接主服务器之后,SYNC命令创建包含k1-k10089的RDB文件。而事实上只需要再同步断线后的k10087-k10089即可。SYNC的“全同步”对于从服务来说是不必要的。
SYNC命令非常消耗资源,原因有三点:
1)主服务器执行BGSAVE命令生成RDB文件,这个生成过程会大量消耗主服务器资源(CPU、内存和磁盘I/O资源)
2)主服务器需要将自己生成的RBD文件发送给从从服务器,这个发送操作会消耗主从服务器大量的网络资源(带宽与流量)
3)接收到RDB文件你的从服务器需要载入RDB文件,载入期间从服务器会因为阻塞而导致没办法处理命令请求。
二、新版复制功能
为了解决旧版本中断线情况下SYNC低效问题,在Redis 2.8之后使用PSYNC命令代替SYNC命令执行复制同步操作,自然PSYNC具备完整重同步和部分重同步模式
1)完整重同步:跟旧版复制基本是一致的,可以理解为“全量”复制。
2)部分重同步:在命令传播阶段,断线重复制只需要发送主服务器在断开期间执行的写命给从服务器即可,可以理解为“增量”复制。
断开连接后发送+CONTINUE回复,表示使用PSYNC部分重同步,只需要同步k10087-10089即可,不需要生成RDB文件
相关流程图如下:
三、复制的实现
Redis不管是旧版还是新版,复制的实现都可以分为七个步骤,流程图如下:
1、设置主服务的地址与端口
当客户端向从服务器发送一下命令时或者在配置文件中配置slaveof选项
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
从服务器将向发送SLAVEOF命令的客户端返回OK,表示复制指令已经被接收,而实际上复制工作是在OK返回之后进行。
2、建立套接字连接
从服务器根据设置的套接字创建连向主服务器的套接字连接
主服务器接收从服务器的套接字连接之后,为该套接字创建响应的客户端状态,并将此时的从服务器看做是主服务器的客户端,也就是该从服务器同时具备服务器与客户端两个身份。
3、发送PING命令
从服务器成为主服务器的客户端之后,做的第一件事就是向主服务器发送PING命令。PING命令主要有两种作用:
1) 虽然建立了套接字连接,但是还未使用过,通过发送PING命令检查套接字的读写状态是否正常
2) 通过发送PING命令检查主服务器能否正常处理命令请求
从服务器在发送PING命令之后将遇到以下三种情况的其中一种:
1) 主服务器向从服务器返回一个回复,但是从服务器却不能在规定的会时间(timeout)内读取命令回复的内容,则表示当前主从服务器之间的网络状态连接不佳,不能基础执行复制工作的后续步骤,这时从服务器会断开套接字连接重新创建。
2) 主服务器向从服务器返回一个错误,那么表示主服务器暂时没有办法处理从服务器的命令请求,不能继续执行复制工作的后续步骤,这时从服务器会断开套接字连接重新创建。
3) 如果从服务器读取到“PONG”回复,那么表示主从服务器之间网络连接正常,并且主服务器可以处理从服务器发送的命令请求。
4、身份验证
从服务器接收到主服务器返回的“PONG”回复,接下来就需要考虑身份验证的事。
如果从服务器设置了masterauth选项,那么进行身份验证
如果从服务器没有设置masterauth选项,那么不进行身份验证
从服务器在身份验证的时候可能遇到三种情况
1) 主服务没有设置requirepass选项,并且从服务器也没有设置masterquth选项,那么主服务器继续执行从服务器命令,完成复制工作
2) 如果从服务器通过AUTH命令发送的密码与主服务器中requirepass密码相同,那么主服务器将继续执行从服务器发送的命令,复制工作继续,与此相反,密码不一致,则会返回invalid password错误
3) 如果从服务器没有设置masterauth选项,而主服务器设置了requirepass选项,那么主服务器将返回一个NOAUTH错误。反之没有设设置masterauth选项,而设置了requirepass选项,那么会返回no password is set错误。
5、发送端口信息
在身份验证步骤之后,从服务器将执行命令REPLCONF listening-port <port>,向主服务器发送从服务器的监听端口号。
6、同步
就是上述所指的同步操作,从服务器向主服务器发送PSYNC命令,执行同步操作,值得注意的是只有从服务器是主服务器的客户端,但是执行同步操作之后,主服务器也会成为从服务器的客户端。
7、命令传播
主从服务器就会进入命令传播阶段,主服务器只要将自己执行的写命令发送给从服务器,而从服务器只要一直执行并接收主服务器发来的写命令(上述已经介绍过,这里不过多介绍)