Redis如何高效可靠地实现主从复制?终于有人讲明白了( 二 )


另外需要注意的是,每台Redis服务器都有一个运行ID,从服务器每次发送psync请求同步数据时,会携带自己需要同步主服务器的运行ID 。
主服务器接收到psync命令时,需要判断命令参数运行ID与自己的运行ID是否相等,只有相等才有可能执行部分重同步 。而当从服务器首次请求主服务器同步数据时,从服务器显然是不知道主服务器的运行ID,此时运行ID以“?”填充,同时复制偏移量初始化为-1 。
从上面的分析我们可以得到psync命令格式为“psync <MASTER_RUN_ID> <OFFSET>”,主从复制初始化流程如图1所示 。
从图1可以看到,当主服务器判断可以执行部分重同步时向从服务器返回“+CON-TINUE”;需要执行完整重同步时向从服务器返回“+FULLRESYNC RUN_ID OFFSET”,其中RUN_ID为主服务器自己的运行ID,OFFSET为复制偏移量 。

Redis如何高效可靠地实现主从复制?终于有人讲明白了

文章插图
▲图1 主从复制初始化流程图
可以看到执行部分重同步的要求还是比较严格的:
  1. RUN_ID必须相等;
  2. 复制偏移量必须包含在复制缓冲区中 。
然而在生产环境中,经常会出现以下两种情况:
  • 从服务器重启(复制信息丢失);
  • 主服务器故障导致主从切换(从多个从服务器重新选举出一台机器作为主服务器,主服务器运行ID发生改变) 。
这时显然是无法执行部分重同步的,而这两种情况又很常见,因此Redis 4.0针对主从复制又提出了两点优化,提出了psync2协议 。
  • 方案1:持久化主从复制信息
Redis服务器关闭时,将主从复制信息(复制的主服务器RUN_ID与复制偏移量)作为辅助字段存储在RDB文件中;Redis服务器启动加载RDB文件时,恢复主从复制信息,重新同步主服务器时携带 。持久化主从复制信息代码如下:
if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid) == -1) return -1; if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset) == -1) return -1;
  • 方案2:存储上一个主服务器复制信息
当主服务器发生故障,自己成为新的主服务器时,使用变量server.replid2和server.second_replid_offset存储之前主服务器的运行ID与复制偏移量:
void shiftReplicationId(void) { memcpy(server.replid2,server.replid,sizeof(server.replid)); server.second_replid_offset = server.master_repl_offset+1; changeReplicationId();}另外判断是否能执行部分重同步的条件也改变为:
if (strcasecmp(master_replid, server.replid) && (strcasecmp(master_replid, server.replid2) || psync_offset > server.second_replid_offset)){ goto need_full_resync;}假设m为主服务器(运行ID为M_ID),A、B和C为三个从服务器;某一时刻主服务器m发生故障,从服务器A升级为主服务器(同时会记录replid2=M_ID),从服务器B和C重新向主服务器A发送“psync M_ID psync_offset”请求;显然根据上面条件,只要psync_offset满足条件,就可以执行部分重同步 。
关于作者:李乐,好未来php工程师,西安电子科技大学硕士,乐于钻研技术与源码研究,对Redis和Nginx有较深理解 。合著书籍《Redis 5设计与源码分析》 。
本文摘编自《Redis 5设计与源码分析》,经出版方授权发布 。

【Redis如何高效可靠地实现主从复制?终于有人讲明白了】


推荐阅读