为了保证服务的可用性,现代数据库都提供了复制功能,同时在多个进程中维护一致的数据状态。
Redis 支持一主多从的复制架构,该功能被简化成了一条 SLAVEOF 命令,下面通过条命令来解析 Redis 的主从复制机制。
在本机上通过 redis-server 启动两个服务,然后通过 tcpdump 观察主从间的交互情况:
redis-server --port 6379 --requirepass 123456 # 启动 master redis-server --port 6380 --masterauth 123456 # 启动 slave tcpdump -t -i lo0 host localhost and port 6379 | awk -F ']' '{print $1"]"$3}' # 在 localhost:6380 上执行 SLAVEOF localhost 6379 建立同步连接,进入 Full-ReSync 阶段 localhost.59297 > localhost.6379: Flags [S] localhost.6379 > localhost.59297: Flags [S.] localhost.59297 > localhost.6379: Flags [P.] "PING" localhost.6379 > localhost.59297: Flags [P.] "NOAUTH Authentication required." localhost.59297 > localhost.6379: Flags [P.] "AUTH 123456" localhost.6379 > localhost.59297: Flags [P.] "OK" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF listening-port 6380" localhost.6379 > localhost.59297: Flags [P.] "OK": localhost.59297 > localhost.6379: Flags [P.] "REPLCONF capa eof" localhost.6379 > localhost.59297: Flags [P.] "OK": localhost.59297 > localhost.6379: Flags [P.] "PSYNC ? -1" localhost.6379 > localhost.59297: Flags [P.] "FULLRESYNC 8efb6ca4edf1258c05a5ced43b0c73fe4deb1908 1" localhost.6379 > localhost.59297: Flags [P.] [|RESP: localhost.6379 > localhost.59297: Flags [P.] "REDIS0007M-z^Iredis-ver^F3.2.11M-z" [|RESP # 完成 Full-ReSync 后进入 Propagation 阶段 localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "1" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "1" localhost.6379 > localhost.59297: Flags [P.] "PING" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "15" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "15" localhost.6379 > localhost.59297: Flags [P.] "SELECT" "0" "SET" "KEY" "VALUE" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "85" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "85" localhost.6379 > localhost.59297: Flags [P.] "SET" "KEY2" "VALUE2" localhost.6379 > localhost.59297: Flags [P.] "MSET" "KEY3" "VALUE3" "KEY4" "VALUE4" "KEY5" "VALUE5" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "256" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "256" localhost.6379 > localhost.59297: Flags [P.] "PING" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "270" localhost.59297 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "270" # 在 localhost:6380 上执行 DEBUG SLEEP 60 模拟网络中断的情况 localhost.6379 > localhost.59297: Flags [P.] "PING" localhost.6379 > localhost.59297: Flags [P.] "SET" "KEY6" "VALUE6" localhost.6379 > localhost.59297: Flags [P.] "SET" "KEY7" "VALUE7" localhost.6379 > localhost.59297: Flags [P.] "PING" localhost.6379 > localhost.59297: Flags [P.] "MSET" "KEY8" "VALUE8" "KEY9" "VALUE9" localhost.6379 > localhost.59297: Flags [P.] "PING" localhost.6379 > localhost.59297: Flags [P.] "PING" localhost.59297 > localhost.6379: Flags [.] localhost.59297 > localhost.6379: Flags [R.] # 旧的同步连接断开后重新建立同步连接,进入 Partical-ReSync 阶段 localhost.59313 > localhost.6379: Flags [S] localhost.6379 > localhost.59313: Flags [S.] localhost.59313 > localhost.6379: Flags [P.] "PING" localhost.6379 > localhost.59313: Flags [P.] "NOAUTH Authentication required." localhost.59313 > localhost.6379: Flags [P.] "AUTH 123456" localhost.6379 > localhost.59313: Flags [P.] "OK" localhost.59313 > localhost.6379: Flags [P.] "REPLCONF listening-port 6380" localhost.6379 > localhost.59313: Flags [P.] "OK" localhost.59313 > localhost.6379: Flags [P.] "REPLCONF capa eof" localhost.6379 > localhost.59313: Flags [P.] "OK" localhost.59313 > localhost.6379: Flags [P.] "PSYNC 8efb6ca4edf1258c05a5ced43b0c73fe4deb1908 271" localhost.6379 > localhost.59313: Flags [P.] "CONTINUE" localhost.6379 > localhost.59313: Flags [P.] "PING" "PING" "SET" "KEY6" "VALUE6" "PING" "SET" "KEY7" "VALUE7" "PING" "MSET" "KEY8" "VALUE8" "KEY9" "VALUE9" "PING" "PING" localhost.59313 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "519" localhost.59313 > localhost.6379: Flags [P.] "REPLCONF" "ACK" "519" localhost.6379 > localhost.59313: Flags [P.] "PING" localhost.59313 > localhost.6379: Flags [P.]: "REPLCONF" "ACK" "533" localhost.59313 > localhost.6379: Flags [P.]: "REPLCONF" "ACK" "533"整个过程可以分为 Full-ReSync, Command-Propagate, Partical-ReSync 总共 3 阶段:
+----------------------+ +---------------------+ | redisServer (master) | | redisServer (slave) | | localhost:6379 | | localhost:6380 | +----------------------+ +---------------------+ | slaves | | master | +----------------------+ +---------------------+ | | +----------------+ +-------------+ | redisClient[?] | | redisClient | +----------------+ +-------------+ | ^ <<<<<<<<<<<<<<<<<< PING <<<<<<<<<<<<<<<<< | | Step 1 : 检查套接字与 master 状态 | >>>>>>>>>>>>> PONG / NOAUTH >>>>>>>>>>>>> | | | <<<<<<<<<<<<<<<<<< AUTH <<<<<<<<<<<<<<<<< | | Step 2 : 身份验证 | >>>>>>>>>>>>>>>>>>> OK >>>>>>>>>>>>>>>>>> | | | <<<< REPLCONF listening-port [port] <<<<< | | Step 3 : 发送 slave 端口 Full-ReSync >>>>>>>>>>>>>>>>>>> OK >>>>>>>>>>>>>>>>>> | | | <<<<<< REPLCONF capa [eof|psync2] <<<<<<< | | Step 4 : 检查命令兼容性 | >>>>>>>>>>>>>>>>>>> OK >>>>>>>>>>>>>>>>>> | | | <<<<<<<<<<<<<< PSYNC ? -1 <<<<<<<<<<<<<<< | | | >>>>>> FULLRESYNC [replid] [offset] >>>>> Step 6 : 执行全量同步 | V | BGSAVE | V v >>>>>>>>>>>>> RDB Snapshot >>>>>>>>>>>>>> ^ <<<<<<<<< REPLCONF ACK [offset] <<<<<<<<< | >>>>>>>>>>>>>>> COMMAND 1 >>>>>>>>>>>>>>> | >>>>>>>>>>>>>>> COMMAND 2 >>>>>>>>>>>>>>> | <<<<<<<< REPLCONF ACK [offset+?] <<<<<<<< 心跳检测 & 命令传播 Command-Propagate >>>>>>>>>>>>>>>>>> PING >>>>>>>>>>>>>>>>> | >>>>>>>>>>>>>>> COMMAND 3 >>>>>>>>>>>>>>> | <<<<<<<< REPLCONF ACK [offset+?] <<<<<<<< | <<<<<<<< REPLCONF ACK [offset+?] <<<<<<<< v >>>>>>>>>>>>>>>>>> PING >>>>>>>>>>>>>>>>> ^ ========================================= | ====== The Same With Full-ReSync ======== | ========================================= | | Partical-ReSync <<<<<<<< PSYNC [replid] [offset] <<<<<<<< 重连后执行部分同步 | | | >>>>>>>>>>>>>>> CONTINUE >>>>>>>>>>>>>>>> | >>>>>>>>>>>>>>> COMMAND N >>>>>>>>>>>>>>> v >>>>>>>>>>>>>>> COMMAND ... >>>>>>>>>>>>> PSYNC 命令