type
status
date
slug
summary
tags
category
icon
password
概念
Redis replication 是一种 master-slave 模式的复制机制,这种机制使得 slave 节点可以成为与 master 节点完全相同的副本,它的最大特点就是保持 master 和 slave 节点的最终一致性。它的优点是:
- 读写分离:一个 master 用于写,多个 slave 用于分摊读的压力
- 高可用:如果 master 挂掉了,可以提升一个 slave 为新的 master,进而实现故障转移(failover)
- 主从同时对外提供读服务:Sentinel / Cluster 中的从节点,官方默认设置的是不分担读请求的、只作备份和故障转移用,当有请求读向从节点时,会被重定向对应的主节点来处理。这是 Replication 与其它部署方式不一样的地方
缺点如下:
- 虽然 Replication 是一主多从的架构模式,但无论写能力还是存储能力都受到单机的限制,如果要拓展这方面的能力,可以考虑分片和 Pika
- 高可用性不足:当 master 故障的时候需要人为将 slave 提升为从节点,要解决这方面的问题需要再搭配额外的 HA 系统,比较繁琐
适用场景:
由于 Replication 单个 master 无法承载大数据量且可用性不高,不适用于日常的企业级开发。
使用
用户可以通过执行 replicaof(Redis 5.0 之前使用 slaveof)命令或者在配置文件中设置 slaveof 选项,令从服务器去复制主服务器。
实现原理
旧版
在 2.8 版本以前使用的复制功能是由同步 (sync) 和命令传播 (command propagate) 两个操作来实现的。
- 同步:对从服务器的数据库状态更新,执行完成后使其和主服务器的数据库状态一致,具体操作:
a. 从服务器向主服务器发送 SYNC 命令
b. 主服务器开始使用 BGSAVE 命令生成 RDB 文件,同时用缓存区记录生成 RDB 文件期间所有的写命令
c. 主服务器 BGSAVE 执行完成,并开始向从服务器发送生成的 RDB 文件
d. 从服务器接收并载入收到的 RDB 文件
e. 主服务器向从服务器发送缓存区里记录的写命令
f. 从服务器接收并执行收到的写命令
- 命令传播:在主服务器被修改后,导致主从服务器的数据库状态不一致时,主服务器会将自己在同步完成之后执行的命令发给从服务器,从服务器执行后就又回到一致性状态。
缺陷
在 Redis 中复制功能存在两种情况:初次复制和断线后重复制。两者的区别就是从服务器由于网络原因中断前一次是否参与过复制或者复制的是否是同一台主服务器。旧版的复制功能可以很好地解决初次复制的问题,在对于断线后重复制虽然也可以令两者再次处于同一状态,但效率很低。如果仍然使用旧版的复制功能相当于在主从服务器断线重连之后再进行一次初次复制操作。
新版
从 2.8 版本开始,Redis 使用 PSYNC 命令来代替 SYNC 执行同步操作。其具有完整重同步和部分重同步两种模式。
- 完整重同步:用于处理初次复制的情况,和 SYNC 执行步骤基本一致
- 部分重同步:用于处理断线后重复制的情况,当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器将主从服务器连接断开期间执行的写命令发送给从服务器。
PSYNC 实现部分重同步
在命令传播阶段可以看到,PSYNC 和 SYNC 的区别就在于对部分重同步情况下的处理方式不同。它主要依靠以下三个部分来实现部分重同步:
- 主从服务器的复制偏移量 ( replication offset )
主服务器和从服务器分别维护一个复制偏移量,这是为了知道主从服务器数据库同步进度。如果某从服务器断线重连后发送 PSYNC 命令,那如何来判断是完整重同步还是部分重同步呢?若是部分重同步又该从哪条命令开始?复制积压缓存区可以解决。
- 主服务器的复制积压缓冲区 ( replication backlog buffer )
由主服务器维护一个默认大小为 1M 的先入先出队列,在命令传播时主服务器不仅仅要向从服务器发送写命令 ( 写入 replication buffer ) 还要在内部缓存区保存最近的写命令。当从服务器再次发送 PSYNC 命令时根据其携带的复制偏移量和复制积压缓存区作对比:如果偏移量之后的数据在缓存区里那么主服务器返回一个 +CONTINUE 消息后执行部分重同步,否则返回一个 +FULLRESYNC 消息后执行完整重同步。repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。因此,要想避免这一情况,一般可以调整 repl_backlog_size 这个参数。这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。
- 服务器的运行ID ( runid )
在执行初次复制的时候主服务器就会向从服务器发送自己的服务器运行 ID (40 个随机的十六进制字符组成),在断线重连后从服务器就会向主服务器发送自己之前保存 ID,经主服务器对比后,若相同则执行部分重同步,否则执行完整重同步。注意:服务器每重启一次其 runid 就会更换
PSYNC 的使用
一张图总结 PSYNC 执行过程中可能遇到的情况
实现流程
- 设置主服务器的地址和端口
根据 SLAVEOF 命令中指定的IP地址和端口号,设置当前从服务器状态中的主服务器地址属性 masterhost 和主服务器端口属性 masterport ( redisServer->masterhost/masterport )。设置完成之后,便向客户端返回 OK,而实际的复制工作则是在返回 OK 以后才开始执行的,所以 SLAVEOF 是一个异步命令。
- 建立套接字连接
从服务器根据 SLAVEOF 命令中指定的 IP 地址和端口号,创建连向主服务器的套接字连接。连接成功之后,从服务器将为这个套接字关联一个文件事件处理器,专门用于处理复制工作。比如,接收主服务器传送的 RDB 文件和写命令。而主服务器则为该套接字创建相应的客户端状态,并将从服务器当作一个连接到主服务器的客户端来对待。
- 发送 PING 命令
从服务器成为主服务器的客户端之后,做的第一件事就是向主服务器发送一个 PING 命令。作用如下:
a. 检查套接字的读写状态是否正常;
b. 检查主服务器能否正常处理命令请求。
- 身份验证
如果从服务器没有设置了 masterauth 选项,则不进行身份验证。如果从服务器设置了 masterauth 选项,则需要向主服务器发送 AUTH 命令进行身份验证:AUTH < masterauth 选项配置的值 >。主服务器接收到 AUTH 命令发送的密码之后,将其与自身的 requirepass 选项设置的密码进行对比,来决定身份验证是否通过
- 发送端口信息
这一步并不重要。从服务器向主服务器发送从服务器的监听端口号:REPLCONF listening-port < port_number >,主服务器接收到以后将其记录在从服务器对应的客户端状态 ( redisCient->slave_listening_port ) 中。该属性目前唯一的作用就是在主服务器执行 INFO replication 命令时打印出从服务器的端口号。
在同步操作执行之前,只有从服务器是主服务器的客户端,但是在执行同步操作之后,主服务器也会称为从服务器的客户端
- 同步
a. 如果PSYNC执行的是完全重同步,那么主服务器需要成为从服务器的客户端,才能将保存在缓冲区里面的写命令发送给从服务器执行
b. 如果PSYNC执行的是部分重同步,那么主服务器需要成为从服务器的客户端,才能向从服务器发送保存在复制积压缓冲区里面的写命令
- 命令传播
刚开始时已经说了,命令传播要结合心跳机制一起看
网上找的流程图,凑合着看
心跳机制
在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:REPLCONF ACK < replication_offset >。其中replication_offset 是从服务器当前的复制偏移量
- 检测主从服务器的网络连接状态
- 辅助实现 min-slaves 选项
min-slaves-to-write 和 min-slaves-max-lag 防止主服务器在不安全的情况下执行写命令。 在从服务器数量少于3个,或者三个从服务器的延迟 ( lag ) 都大于或等于10秒时,主服务器将拒绝执行写命令 min-slaves-to-write 3 min-slaves-max-lag 10
- 检测命令丢失。偏移量不一致,会进行补发缺失数据的操作(Redis 2.8 之前会命令丢失)
参考文章
- Redis 设计与实现