Psync命令的实现
前面我们提到过,现在Redis进行复制,从服务器是使用了Psync命令代替了Sync命令,下面介绍一下Psync命令的实现
Psync的调用方式有两种
- 从服务器没有复制过主服务器,或者从服务器执行了slaveof no one命令,取消了从主关系,那么当从服务器执行slaveof命令时,会发送Psync ? -1命令,主动请求主服务器进行完整重同步(第一次同步肯定要执行完整重同步)
- 从服务器已经复制过主服务器,但是中途断开连接了,进行重新连接时,也是发送Psync命令,命令格式为如下:Psync ,runid就是前面提到过的从服务器保留主服务器的运行ID,offset就是复制偏移量,这样主服务器就可以对进行比对,看是否需要执行完整重同步,再去看复制积压缓冲池里面是否还存有该复制偏移量往后的数据,如果有,就进行部分重同步。
根据情况的不同,接收到Psync命令的主服务器会向从服务器返回以下三种回复之一
-
如果主服务器返回+FULLRESYNC 回复给从服务器,则代表主服务器将要进行完整重同步,runid是主服务器的运行ID,从服务器接受到这个运行ID之后就会保存起来,在下一次需要发送Psync命令时试用,而offset就是主服务器当前的复制偏移量,从服务器此时会将这个值作为自己的复制偏移量初值,然后进行维护起来(前面提到过,主从服务器都会维护自己的一个复制偏移量),此时从服务器需要等待主服务器发送RDB文件,载入后,再等待主服务器发送缓冲池在空白期执行的写命令,然后执行这些写命令,至此完成同步。
-
如果主服务器返回的是+CONTINUE回复,那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了(主服务器从复制积压缓冲池中取出)
-
如果主服务器返回-ERR回复,表示版本不匹配,主服务器的版本太低(低于2.8),无法识别从服务器发送的Psync命令,但并不会停止同步,而是从服务器会向主服务器发送Sync命令,完成同步操作
过程如下图所示
复制/同步的实现
上面介绍了Psync命令的过程,但没有具体到其如何进行同步和复制的实现
整个复制同步过程总共有7个步骤
- 设置主服务器的地址和端口
- 建立套接字连接
- 发送Ping命令
- 身份验证
- 发送端口信息
- 同步
- 命令传播
步骤1:设置主服务器的地址和端口
当客户端向从服务器发送一下命令时:
slaveof 127.0.0.1 6379
首先要做的就是将客户端给定的主服务器IP地址以及端口号保存到服务器状态的masterhost属性和masterport属性里面
Stcuct redisServer{
//...
前面的一些信息
//主服务器地址
char *masterhost;
//主服务器端口号
int masterport;
//....
};
SLAVEOF命令也是一个异步命令,会启用另一个进程去执行,在完成保存主服务器IP地址和端口号的工作后,从服务器将发送OK回复给客户端
这一步只是代表复制指令被接收,下面才会真正去执行复制工作
步骤二:建立套接字连接
得到了主服务器IP地址和端口号,下面的一步肯定是要进行主从服务器之间连接
从服务器会自己新建一个套接字,然后使用这个套接字连接到服务器,如果连接成功,收到连接成功回复的从服务器套接字会自动去绑定专门用于处理复制工作的文件事件处理器,这个处理器将会负责执行后续的复制工作,比如去接收RDB文件、接收后面主服务器通过传播发送的写命令
而主服务器在接受从服务器套接字连接之后,会为该套接字创建相应的一个客户端状态,将从服务器视作一个客户端去对待,那么此时从服务器就可以向主服务器发送命令请求,而主服务器可以接受从服务器发送的命令请求进行返回命令回复(不熟悉套接字连接可以回看前面系列的套接字连接)
步骤3:发送Ping命令
完成连接之后,从服务器就成为了主服务器的一个客户端,从服务器第一件事就是去发送Ping命令,一般客户端向服务器发送Ping命令,服务器会返回Pong的信息
发送ping命令主要有两个作用
- 虽然主从服务器建立了套接字连接,但主从服务器并没有使用套接字进行过任何通信,难以确定状态是否正常,使用ping命令可以知道连接状态是否正常
- 同时,Ping命令可以确定主服务器可以正常执行命令,因为复制工作接下来的几个步骤都必须在主服务器可以正常执行命令的情况下进行
从服务器在发送Ping命令之后将遇到下列三种情况之一
-
第一种是网络状态不佳,主服务器接收到了Ping已经回复了pong,但从服务器不可以在规定时限内读取出命令回复的内容,这样就表示主从服务器之间的网络连接状态不佳,不可以继续执行复制工作的后续步骤,当出现这种情况时,从服务器会断开连接,然后重新创建连接主服务器的套接字。
-
第二种是主服务出错,主服务器会向从服务器返回一个错误,表示主服务器暂时没办法处理从服务器的命令请求,不能够执行复制工作的后续步骤,当出现这种情况时,从服务器也是会断开连接,然后重新创建连接主服务器的套接字。
-
第三种是正常情况,从服务器读取到了Pong回复,表示主从服务器之间连接状态正常并且主服务器可以正常相应从服务器的请求,然后会继续执行下面的复制工作
步骤4:身份验证
现在从服务器可以接收主服务器返回的PONG,证明连接一切正常,下一步就是要进行身份验证了
- 如果从服务器的配置文件设置了masterauth选项,就会进行身份验证
- 如果从服务器的配置文件没有设置masterauth选项,就不会进行身份验证
其实这个身份验证就是从服务器向主服务器发送了一条AUTH命令。
那么对于主服务器这边就会出现4种情况
- 如果主服务器设置了密码验证,即requirepass选项,而且从服务器也设置了masterauth选项,那么就会执行AUTH命令,如果验证通过,复制工作可以继续进行,与此相反,如果验证不通过,主服务器将返回一个invalid passwor的错误
- 如果主服务器设置了密码验证,但从服务器没有设置了masterauth选项,主服务器会返回一个NOAUTH错误
- 如果主服务器没有设置密码验证,但从服务器设置了masterauth选项,主服务器也将返回一个no password is set 的错误
- 如果主服务器没有设置密码验证,从服务器也没有设置masterauth选项,继续执行命令,继续下面的复制工作
步骤5:发送端口信息
在进行身份验证后,从服务器将执行下列的命令
REPLCONF listening-port <port-number>
其实就是从服务器将自己的监听端口号发送给主服务器,主服务器在接收到这个命令之后,就会将该监听端口号保存在主服务器上的客户端状态,对应的属性为slave_listening_port属性中
typedef struct redisClient(
//。。。
//从服务器的监听端口号
int slave_listening_port;
//..
)redisClient;
这个属性目前并没有什么作用,目前唯一的作用就是主服务器接收客户端的info repliication命令时得到的回复中会显示,下图中对应的port属性就是了(可能是主服务器那边需要记录自己的从服务器的信息)
步骤6:同步
来到了这里,从服务器在主服务器中的客户端信息已经完整,并且主从服务器之间连接没有问题,就可以进行同步操作了。
这里要注意的是,在同步步骤之前,前面的所有步骤,从服务器都是主服务器的客户端,但来到了这一步,从服务器不仅是主服务器的客户端,同时,主服务器也是从服务器的客户端,这是因为后面的缓冲区里面的写命令是由主服务器发送给从服务器的,此时只要主服务器为从服务器的客户端,那么直接发送写命令给从服务器去执行即可。
- 如果Psync命令执行的是部分重同步操作,主服务器需要成为从服务器的客户端,才可以发送缓冲池的写命令
- 如果Psync命令执行的是完全重同步操作,主服务也需要成为从服务器的客户端,才可以发送缓冲池的写命令
- 总的来说,来到同步这一步,主服务器也会变成从服务器的客户端
上一文章已经说过部分重同步和完整重同步的实现,这里就不再赘述
因此,在同步操作执行之后,主从服务器双方都是对方的客户端,他们可以互相向对方发送命令请求,互相向对方发送命令结果返回。
步骤7:命令传播
当完成了同步之后,此时主从数据库的状态是一致的,但为了让之后也可以保持一致,就需要进行命令传播,这时主服务器只要一直将自己执行的写命令发送给从服务器(主从服务器已经可以互相请求了),而从服务器只要一直接收并且执行主服务器发送来的写命令,就可以保证主从服务器状态一致了。