主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。 通过再B服务器上执行replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>命令,A服务器会变成B服务器的主服务。
1.主从服务器的第一次同步--全量
主从服务器间的第一次同步的过程可分为三个阶段:
- 第一阶段是建立链接、协商同步;
- 第二阶段是主服务器同步数据给从服务器;
- 第三阶段是主服务器发送新写操作命令给从服务器。 主从数据同步过程中产生的写命令;
第一阶段:建立链接、协商同步--建立的连接属于长链接,之后的命令同步都是通过该链接进行
执行了 replicaof 命令后,从服务器就会给主服务器发送 psync 命令,表示要进行数据同步。psync 命令包含两个参数,分别是主服务器的 runID 和复制进度 offset。
-
runID,每个 Redis 服务器在启动时都会自动生产一个随机的 ID 来唯一标识自己。当从服务器和主服务器第一次同步时,因为不知道主服务器的 run ID,所以将其设置为 "?"。
-
offset,表示复制的进度,第一次同步时,其值为 -1。
主服务器收到 psync 命令后,会用 FULLRESYNC 作为响应命令返回给对方。并且这个响应命令会带上两个参数:主服务器的 runID 和主服务器目前的复制进度 offset。从服务器收到响应后,会记录这两个值。FULLRESYNC 响应命令的意图是采用全量复制的方式,也就是主服务器会把所有的数据都同步给从服务器。所以,第一阶段的工作时为了全量复制做准备。
第二阶段:主服务器同步数据给从服务器
接着,主服务器会执行bgsave命令来生成RDB文件(默认为该方式,也可以是AOF文件,但是第一次全量的的时候最好是rdb,二进制节省传输带宽,执行快),然后把文件发送给从服务器。从服务器收到RDB文件后,会先清空当前的数据,然后载入 RDB 文件。这里有一点要注意,主服务器生成 RDB 这个过程是不会阻塞主线程的,因为 bgsave 命令是产生了一个子进程来做生成 RDB 文件的工作,是异步工作的,这样 Redis 依然可以正常处理命令。但是,这期间的写操作命令并没有记录到刚刚生成的 RDB 文件中,这时主从服务器间的数据就不一致了。
那么为了保证主从服务器的数据一致性,主服务器在下面这三个时间间隙中将收到的写操作命令,写入到 replication buffer {所有同步到从服务器的命令都会经过该缓冲区}缓冲区里:
-
主服务器生成 RDB 文件期间;
-
主服务器发送 RDB 文件给从服务器期间;
-
「从服务器」加载 RDB 文件期间;
第三阶段:主服务器发送新写操作命令给从服务器
在主服务器生成的 RDB 文件发送完,从服务器收到 RDB 文件后,丢弃所有旧数据,将 RDB 数据载入到内存。完成 RDB 的载入后,会回复一个确认消息给主服务器。接着,主服务器将 replication buffer 缓冲区里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区里发来的命令,这时主从服务器的数据就一致了。
2.主从服务器第一次同步完成后,之后的写命令如何同步
主从服务器在完成第一次同步后,就会基于长连接进行命令传播。具体流程如下:
-
接收命令:当主节点接收到一个写命令时,首先会将该命令写入 Replication Buffer(实时数据)。这是因为 Replication Buffer 主要用于缓存当前的写操作,以便能够迅速将这些命令发送给连接的从节点。
-
发送命令:主节点会尝试立即将 Replication Buffer 中的命令发送给其所有活跃的从节点。如果从节点连接正常,命令会被迅速传递。
如果再整个架构中,系统一切都正常,那么主从同步就可以按照上面的流程正常运转下去,可是,网络总是不按套路出牌的嘛,说延迟就延迟,说断开就断开。如果主从服务器间的网络连接断开了,那么就无法进行命令传播了,这时从服务器的数据就没办法和主服务器保持一致了,客户端就可能从「从服务器」读到旧的数据。那么redis是如何处理的呢?依靠的就是增量复制。
3.增量复制
在Redis 2.8之前,如果主从服务器在命令同步时出现了网络断开又恢复的情况,从服务器就会和主服务器重新进行一次全量复制,很明显这样的开销太大了,必须要改进一波。所以,从Redis 2.8开始,网络断开又恢复后,主从服务器会采用增量复制的方式继续同步,也就是只会把网络断开期间主服务器接收到的写操作命令,同步给从服务器。这里就离不开一个关键的缓存池:repl_backlog_buffer。
1.什么是repl_backlog_buffer
Repl Backlog Buffer 是用来处理从节点与主节点之间的网络延迟和临时断开的情况。它会缓存主节点的命令,以便在从节点重新连接后能够获取这些命令并进行同步。具体来说,Repl Backlog Buffer 可以存储一定数量的写命令,以便在从节点恢复连接后,能够将这些命令发送给从节点,确保数据的一致性。
其是一个「环形」缓冲区,如果写满会直接覆盖起始位置数据,跟mysql中的redo log双文件类似。其有几个指标比较重要:replication offset,标记上面那个缓冲区的同步进度,主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 来记录自己「写」到的位置,从服务器使用 slave_repl_offset 来记录自己「读」到的位置。
2.repl_backlog_buffer和Replication Buffer的区别
-
实时性:Replication Buffer 专注于存储最新的写命令。这意味着它只会包含在主节点当前的运行周期内接受的写操作,而不会包含历史操作或在从节点断开连接期间的命令。
-
缓冲区的生命周期:当一个新的写命令到达主节点时,该命令会被写入 Replication Buffer。如果从节点连接正常,主节点会立即将这些命令发送到从节点,Replication Buffer 中的命令会被及时消费。
-
存储范围:Replication Buffer 的内容是瞬时的,代表了当前正在进行的写操作。而 Repl Backlog Buffer 则存储的是过去一段时间内的写操作,以便从节点在重新连接后获取这些丢失的命令。
-
目的不同:Replication Buffer 主要是为了提高写操作的实时性和效率,确保主节点能迅速响应写请求。而 Repl Backlog Buffer 则用于处理网络波动或从节点断连的情况,确保从节点在恢复连接后能够同步所有遗漏的写操作。
3.repl_backlog_buffer如何实现增量复制
-
接收命令:当主节点接收到一个写命令时,首先会将该命令写入 Replication Buffer(实时数据)。这是因为 Replication Buffer 主要用于缓存当前的写操作,以便能够迅速将这些命令发送给连接的从节点。
-
发送命令:主节点会尝试立即将 Replication Buffer 中的命令发送给其所有活跃的从节点。如果从节点连接正常,命令会被迅速传递。
-
写入 Repl Backlog Buffer:同时,主节点也会将相同的写命令写入 Repl Backlog Buffer(历史数据)。这是为了在从节点因网络波动或其他原因暂时断开连接时,能够在其重新连接后补发这些命令,确保数据的一致性。
上面的流程即是一个写命令的正常同步流程,此时发生了网络中断,1分钟以后网络恢复,那么这1分钟内发生的写命令已经同步到主节点的Repl Backlog Buffer中,那么如何同步到从节点呢?
-
当从服务器重新连上主服务器时,从服务器会通过 psync 命令将自己的复制偏移量 slave_repl_offset 发送给主服务器
-
主服务器根据自己的 master_repl_offset 和 slave_repl_offset 之间的差距,然后来决定对从服务器执行哪种同步操作:(1)如果判断出从服务器要读取的数据还在 repl_backlog_buffer 缓冲区里,那么主服务器将采用增量同步的方式;(2)如果判断出从服务器要读取的数据已经不存在 repl_backlog_buffer 缓冲区里,那么主服务器将采用全量同步的方式
-
当主服务器在 repl_backlog_buffer 中找到主从服务器差异(增量)的数据后,就会将增量的数据直接发送到从服务器。 无需再经过replication buffer缓存池
所以再主从复制的架构中,为了保证断网后能够进行增量同步,我们需要适当增加repl_backlog_buffer 缓存池的大小。尽可能的大一些,减少出现从服务器要读取的数据被覆盖的概率,从而使得主服务器采用增量同步的方式。
4.repl_backlog_buffer 缓冲区具体要调整到多大呢
估算公式为 second* write_size_per_second, 当然,为了应对一些突发的情况,可以将 repl_backlog_buffer 的大小设置为此基础上的 2 倍。
-
second 为从服务器断线后重新连接上主服务器所需的平均 时间(以秒计算)。
-
write_size_per_second 则是主服务器平均每秒产生的写命令数据量大小。