参考
数据都是存储在一台服务器上,如果出事就完犊子了,比如:
- 如果服务器发生了宕机,由于 数据恢复 是需要点时间,那么 这个期间是无法服务新的请求的;
- 如果这台服务器的 硬盘出现了故障,可能 数据就都丢失了。
要避免这种单点故障,最好的办法是将数据备份到其他服务器上,让这些服务器也可以对外提供服务,这样即使有一台服务器出现了故障,其他服务器依然可以继续提供服务。
Redis 提供了 主从复制 模式,来避免上述的问题。
这个模式可以保证多台服务器的数据一致性,且主从服务器之间采用的是「读写分离」的方式。
主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。

全量复制(第一次同步)
可以使用 replicaof(... 的副本之意。Redis 5.0 之前使用 slaveof)命令形成主服务器和从服务器的关系。
比如,现在有服务器 A 和 服务器 B,我们在服务器 B 上执行下面这条命令:
# 服务器 B 执行这条命令 replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>
主从服务器间的第一次同步的过程可分为三个阶段:

第一阶段:建立链接、协商同步
执行了 replicaof 命令后,从服务器就会给主服务器发送 psync 命令,表示要进行数据同步。
psync 命令包含下面两个参数:
-
- runID,主服务器的 runID。每个 Redis 服务器在启动时都会自动生产一个随机的 ID 来唯一标识自己。当从服务器和主服务器第一次同步时,因为不知道主服务器的 run ID,所以将其设置为 "?"。
- offset,表示复制的进度。第一次同步时,其值为 -1。
主服务器收到 psync 命令后,会用 FULLRESYNC 作为响应命令返回给对方。
并且这个响应命令会带上两个参数:主服务器的 runID 和主服务器目前的复制进度 offset。从服务器收到响应后,会记录这两个值。
FULLRESYNC 响应命令的意图是采用全量复制的方式,也就是第一阶段的工作时为了全量复制做准备。
第二阶段:主服务器同步数据给从服务器
主服务器会执行 bgsave 命令(子进程)来生成 RDB 文件,然后把文件发送给从服务器。
从服务器收到 RDB 文件后,会先清空当前的数据,然后载入 RDB 文件。
但是,这期间的写操作命令并没有记录到刚刚生成的 RDB 文件中,主服务器需要在下面这三个时间间隙中将收到的写操作命令,写入到 replication buffer 缓冲区里(类似地,AOF 重写时期间写入的是 aof_rewrite_buf):
-
- 主服务器生成 RDB 文件期间;
- 主服务器发送 RDB 文件给从服务器期间;
- 「从服务器」加载 RDB 文件期间;
第三阶段:主服务器发送新写操作命令给从服务器
在主服务器生成的 RDB 文件发送完,从服务器收到 RDB 文件后,丢弃所有旧数据,将 RDB 数据载入到内存。完成 RDB 的载入后,会回复一个确认消息给主服务器。
接着,主服务器将 replication buffer 缓冲区里所记录的写操作命令 发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区里发来的命令,这时主从服务器的数据就一致了。
至此,主从服务器的第一次同步的工作就完成了。
基于长连接的命令传播(平时)
主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 长连接的,目的是避免频繁的 TCP 连接和断开带来的性能开销。
后续主服务器可以通过这个连接继续将写操作命令传播给从服务器,然后从服务器执行该命令,使得与主服务器的数据库状态相同。
上面的这个过程被称为基于长连接的命令传播,通过这种方式来保证第一次同步后的主从服务器的数据一致性。

分担主服务器的压力(“主-从-从”模式)
主从服务器在第一次数据全量同步的过程中,主服务器会做两件耗时的操作:生成 RDB 文件 和 传输 RDB 文件。
主服务器是可以有多个从服务器的,如果从服务器数量非常多,而且都与主服务器进行全量同步的话,就会带来两个问题:
- 由于是通过 bgsave 命令来生成 RDB 文件的,那么主服务器就会忙于使用 fork() 创建子进程,如果主服务器的内存数据非大,在 执行 fork() 函数时是会阻塞主线程 的,从而使得 Redis 无法正常处理请求;
- 传输 RDB 文件会占用主服务器的网络带宽,会对主服务器响应命令请求产生影响。
Redis 也是一样的,从服务器可以有自己的从服务器,我们可以把拥有从服务器的从服务器当作经理角色,它不仅可以接收主服务器的同步数据,自己也可以同时作为主服务器的形式将数据同步给从服务器,组织形式如下图:

在「从服务器」上执行下面这条命令,使其作为目标服务器的从服务器:
replicaof <目标服务器的IP> 6379
此时如果目标服务器本身也是「从服务器」,那么该目标服务器就会成为「经理」的角色,不仅可以接受主服务器同步的数据,也会把数据同步给自己旗下的从服务器,从而减轻主服务器的负担。
增量复制(网络断开期间)
如果主从服务器间的网络连接断开了,那么就无法进行命令传播了,这时从服务器的数据就没办法和主服务器保持一致了,客户端就可能从「从服务器」读到旧的数据。
如果此时断开的网络,又恢复正常了,要怎么继续保证主从服务器的数据一致性呢?
在 Redis 2.8 之前,如果主从服务器在命令同步时出现了网络断开又恢复的情况,从服务器就会和主服务器重新进行一次全量复制,很明显这样的开销太大了,必须要改进一波。
所以,从 Redis 2.8 开始,网络断开又恢复后,从主从服务器会采用 增量复制 的方式继续同步,也就是只会把 网络断开期间主服务器接收到的写操作命令,同步给从服务器。

从服务器在恢复网络后,会发送 psync 命令给主服务器,此时的 psync 命令里的 offset 参数不是 -1;
- replication buffer 对于一主多从来说是相互独立,即redis会为每一个 redis client 创建一个独立的replication buffer,通过该 buffer 传输数据,
- repl_backlog_buffer 是一个「环形」缓冲区,多个从库共用一个,每一个 redis client 都会有一个 offset,该 offse t除了在 断开重连后会通过 psync 将自己的 offset 传递给master,主从还维护了一个心跳机制,从节点会向主节点发送REPLCONF ACK命令,频率是每秒1次,命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量告知master,repl_backlog_buffer通过该offset进行移动
