主从复制
一:概述
在使用 Redis 时,如果只开启了单个 Redis 服务,那么当服务器宕机时,它在恢复期间是无法处理新来的数据存储请求的,降低了服务的可靠性。为了尽量较少服务的中断,可以将多台服务器进行连接,将数据同时保存在多态服务器中,即使一台服务器发生了宕机,也可以使用其他服务器来对外进行服务,保证业务的正常使用。
在Redis中,让一个服务器去复制另一个服务器中的数据,我们称被复制的服务器为主服务器(master),对主服务器进行复制的称为从服务器(slave),而且数据的复制是单向的,只能由主节点到从节点。需要注意的是,一个matser可以拥有多个slave,而一个slave只能有一个msater。

如果不采用读写分离的方式,那么不管是从库还是主库,都能够接受到客户端的写操作,如果对同一个数据发生了多次修改操作,这些请求都发送在不同的服务器上,就有可能导致数据的不一致了。使用读写分离的方式让主库专注处理客户端发送来的写请求,从库处理客户端发送来的读请求,从而提高服务器的读写负载能力。
主从复制的优点:
- 读写分离:主节点写,从节点读,提高服务器的读写负载能力。
- 负载均衡:- 基于主从结构,配合读写分离,由 slave 分担 master 负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
- 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复。
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案。
二:主从同步
实现主从复制,连接是在从节点发起的,不需要在主节点做任何事。
多个 Redis 之间通过 replicaof 命令形成主库和从库的关系。服务器1(ip:172.16.19.3)和服务器2(ip:172.16.19.5),在服务器2上使用以下命令,服务器2就成了服务器1的从库,并从服务器1中复制数据。
replicaof 172.16.19.3 6379
那么这两个服务器是怎么实现同步的呢。

第一阶段主库和从库建立连接,从库给主库发送 psync 命令,表示要进行数据同步。主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上连参数:主库 runID(每个 Redis 实例启动时都会随机生成一个ID) 和 主库目前的复制进度 offset(-1表示第一次复制),返回给从库,从库收到后记录下这两个参数。注意:FULLRESYNC 响应表示第一次复制采用的全量复制。
第二阶段主库同步数据给从库,主库执行 bgsave 命令,生成 RDB 文件,将文件发送给从库。从库收到文件后,为了避免保存的其他数据的影响,首先将数据库空间清空,然后加载 RDB 文件。因为 bgsave 会创建一个子进程写入 RDB 文件,所有不会造成主库的阻塞。
第三阶段主库将新写的命令发送给从库,由于在将主库数据同步给从库时,主库不会阻塞,Redis 会在内存中用 replication buffer,将新接收到的写操作记录在其中,这样就实现了主从同步。
综上可以知道,主库中的数据同步到从库时第一次是全量复制,由于主库会执行 bgsave 命令来生成额外的进程来完成写入 RDB 文件,所以主库不会发送阻塞。
三:主从级联模式
在一次全量复制中,主库有两个非常耗时的操作:生成 RDB 文件和传输 RDB 文件。当从库数量较多的时候,都要和主库发送全量复制的话,主库 fork 子进程的操作就会变得频繁,因为fork 操作会阻塞主线程正常请求,所以会导致主库响应应用程序的请求速度变慢。另外传输 RDB 文件也需要占用网络资源。Redis 通过 "主-从-从" 模式将主库生成 RDB 文件和传输 RDB 文件的压力分散到从库上。在部署集群时,设置一个主库,手动的选择一个从库用来级联其他的从库,这个从库相对于被级联的从库来说就是主库。这样这些从库再进行同步时就不需要再和主库进行交互了,这样就可以减少主库上的压力。

四:网络连接
当从库完成了全量复制之后,主库和从库之间就会维护一个网络连接,主库会通过这个连接将之后收到的命令操作再同步给从库,这个过程也被称为基于长连接的命令传播。但是当网络发送中断了该怎么办呢。
在网络发生了中断之后,主从库会采用增量复制的方式继续同步,只把主从库网络中断期间主库收到的命令同步给从库。当主从库断连后,主库会把断连期间收到的写操作命令写入 replication buffer 中,同时也会把这些操作命令写入 repl_backlog_buffer 缓冲区。repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库会记录自己已经读到的位置。主从库连接恢复后,从库给主库发送 psync 命令,并把当前的偏移量 slave_repl_offset 发给主库,主库会判断 master_repl_offset 和 slave_repl_offset 之间的差距,然后进行数据同步。
由于缓冲区是环形的,当缓冲区写满之后,主库继续写入的话会覆盖之间写入的操作,导致数据的不一致。可以调整参数 repl_backlog_size 来设置所需的缓存空间大小。
缓存空间的计算公式:缓存空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。通常 repl_backlog_size = 缓冲空间大小 * 2。
五:扩展
主从库间的数据复制同步使用的是 RDB 文件,但是 AOF 文件相比于 RDB 文件数据丢失更少,操作命令更全,为什么主从库间的复制不使用 AOF 呢。
1.RDB 文件内容是经过压缩的二进制文件,文件很小,而 AOF 文件记录的是每一次的写操作的命令,写操作越多文件越大。主从全量数据同步时会使用网络进行数据传输,应当尽量降低文件的大小。
2.从库在初始化数据的时候,执行 RDB 文件比执行 AOF 文件更快,能够更快的对外提供服务,分担主库的压力。
六:哨兵机制
由于写操作只在主库上进行,当主库发生宕机时,我们就需要找一个运行的新的主库。但是怎么判断主库是不是真的宕机了,应该选择哪个从库作为主库,怎么把新主库的相关信息通知给从库和客户端。
哨兵机制有效的解决了上面的问题,哨兵实际上是一个运行的 Redis 进程,负责监控、选择新主库 和 通知。
6.1 监控
哨兵进程运行时周期性的给所有主从库发送 PING 命令,检查它们是否仍然正常运行,如果主从库没有在规定时间内响应哨兵 PONG,那么会被标记成下线状态,会开启选择新的主库流程。
如果从库对哨兵的 PING 命令响应超时了,会被标记为主观下线。如果是主库没有响应哨兵的 PING 命令时,为了防止误判造成不必要重新选主库的资源浪费,会使用多个哨兵组成集群进行部署,共同决策主库是否真的下线。当有 N个哨兵时,有 N/2+1 个哨兵判定主库下线,那么就表示主库下线成为了一个客观事实,将主库标记为客观下线。
6.2 选择新主库
选择新主库时首先会进行筛选,检查当前从库的在线状态并判断它之前的网络连接状态,如果主从库之间断连次数超过十次,则不适合作为新主库。再给剩下的从库进行打分,通过设置 slave-priority 参数设置从库的优先级,优先级更高的选为新的主库。如果优先级相同进行第二轮打分,判断从库和主库的同步进度,选择主从同步更接近的从库作为新的主库。如果 slave-repl-offset 值一样。则进行第三轮打分,ID号(类似于从库的编号)小的从库得分最高,选择作为最新的主库。
选择新主库的整个过程可以称为 "筛选 + 打分"
通知
哨兵会把新主库的连接信息发送给其他从库,让其他从库执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时哨兵会将新主库的信息发送给客户端,让客户端把请求操作发送给新主库上。
小结



浙公网安备 33010602011771号