06丨数据同步:主从库如何实现数据一致?

  • Q:Redis的主从库和读写分离是怎样的?
    • 读操作:主从库都可接收
    • 写操作:首先到主库执行,然后,主库将写操作同步给从库
  • Q:如何设置主从库?
    • 通过replicaof(redis5.0之前是slaveof)
    • 如现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),我们在实例 2 上 执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:
    • replicaof 172.16.19.3 6379
  • Q:主从库间数据第一次同步的三个阶段是怎样?
    • 第一阶段:主从库间建立连接、协商同步,为全量复制做准备
      • 从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了
      • 即从库给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包含了主库的runID和复制进度offset两个参数
        • runID,是每个Redis实例启动时都会自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以将runID设为?
        • offset,此时设为-1,表示第一次复制
      • 主库收到psync命令后,会用FULLRESYNC相应命令带上两个参数:主库runID和主库目前的复制进度offset,返回给从库。从库收到响应后,会记录下这两个参数
      • FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说, 主库会把当前所有的数据都复制给从库
    • 第二阶段:主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的RDB文件
      • 主库执行bgsave命令,生成RDB文件,接着将文件发给从库。从库收到RDB文件后,会先清空当前数据库,然后加载RDB文件。这是因为从库在通过replicaof命令开始和主库同步前,可能保存了其它数据。为了避免之前数据的影响,从库需要先把当前数据库清空
      • 在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
    • 第三阶段:主库会把第二阶段执行过程中新收到的写命令,再发送给从库
      • 当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修 改操作发给从库,从库再重新执行这些操作
  • Q:主从库在命令传播时网络断了之后的处理方法?
    • 采用增量复制的方式继续同步
  • Q:增量复制时,主从库之间的同步方式是怎样?
    • 当主从库断连后,主库会把断连期间收到的写操作命令,写入replication buffer,同时也会把这些操作命令也写入repl_backlog_buffer这个缓冲区
    • repl_backlog_buffer是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置
    • 刚开始的时候,主库和从库的写读位置在一起,这算是它们的起始位置。随着主库不断接 收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置,我们通常用偏移量来衡量这 个偏移距离的大小,对主库来说,对应的偏移量就是 master_repl_offset。主库接收的新写操作越多,这个值就会越大
    • 同样,从库在复制完写操作命令后,它在缓冲区中的读位置也开始逐步偏移刚才的起始位 置,此时,从库已复制的偏移量 slave_repl_offset 也在不断增加。正常情况下,这两个偏 移量基本相等
    • 主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距
    • 在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库就行
    • 注意,因为repl_backlog_buffer是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致
    • 这种情况可调整repl_backlog_size这个参数,这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小=主库写入命令速度操作大小-主从库间网络传输命令速度操作大小
    • 在实际应用中,可把空间扩大一倍,即repl_backlog_size = 缓冲空间大小*2

07丨哨兵机制:主库挂了,如何不间断服务?

  • gt7:哨兵机制是什么?
    • 哨兵其实是一个运行在特殊模式下的Redis进程,和主从库同时运行
    • 用于实现主从库自动切换,有效解决主从复制模式下故障转移的监控、选主、通知三个问题
  • gt7:哨兵机制的基本流程是怎样
    • 监控:哨兵进程在运行时,周期性地给所有的主从库发送PING命令,检测它们是否仍在运行
      • 如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为「下线状态」
      • 如果主库没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线,然后开始自动切换主库的流程
    • 选主:主库挂了后,哨兵就从多个从库里按照一定的规则选择一个从库实例作为新主库
    • 通知:哨兵会把新主库的连接信息发给其它从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上
  • gt7:主观下线是什么?
    • 哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态。如果哨兵发现主库或从库对 PING 命令的响应超时了。哨兵就会先把它标记为「主观下线」
  • gt7:在什么情况下,哨兵会对主库故障产生误判
    • 集群网络压力较大、网络拥塞,或是主库本身压力较大的情况下
  • gt7:什么时候会判断客观下线?
    • 当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为「主观下线」,才能最终判定主库为「客观下线」
  • gt7:如何选定新主库?
    • 筛选+打分
    • 筛选:检查从库当前在线状态和它之前的网络连接状态
      • 通过配置项 down-after-miliseconds * 10,down-after-miliseconds 是认定主从库锻炼的最大连接超时时间。如果在该时间内,主从节点都没有通过网络联系上,可认为主从节点断连了。如果发生断连的次数超过 10 次,说明这个从库的网络状况不好,不适合作为新主库
    • 打分:按照从库优先级、从库复制进度和从库ID号进行三轮打分
      • 1.优先级最高的从库得分高:通过 slave-priority 给从库设置不同优先级
      • 2.和旧主库同步程度最接近的从库得分高:即找从库的slave_repl_offset最接近master_repl_offset的从库
      • 3.ID号小的从库得分高:

08 | 哨兵集群:哨兵挂了,主从库还能切换吗?

  • gt8:哨兵实例之间是怎样互相发现的?
    • 基于 Redis 的 pub/sub (发布/订阅)机制
    • 哨兵只需和主库建立起连接,就可以在主库上发布信息了,如发布它自己的连接信息(IP和端口)。同时,也可从主库上订阅消息,获得其它哨兵发布的连接信息。
    • 当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的 IP 地址和端口
    • 在主从集群中,不同哨兵通过主库上名为__sentinel__:hello的频道来相互发现,实现互相通信的
  • gt8:哨兵是如何知道从库的 IP 地址和端口的呢?
    • 哨兵向主库发送 INFO 命令,主库就把从库列表返回给哨兵
    • 哨兵根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续地对从库进行监控
  • gt8:客户端如何通过监控了解哨兵进行主从切换的过程?
    • 客户端从哨兵的不同频道订阅消息
  • gt8:主库故障以后,哨兵集群有多个实例,怎么确定由哪个哨兵来进行实际的主从切换呢?
  • gt8:判断主库「客观下线」的过程是怎样?
    • 任何一个实例只要自身判断主库「主观下线」后,就会给其它实例发送 is-master-down-by-addr 命令
    • 其它实例会根据自己和主库的连接情况,响应Y(赞成)或N(反对)
    • 一个哨兵获得了仲裁所需的赞成票数后,就可标记主库为「客观下线」。
      • 这个所需的赞成票数是通过哨兵配置文件中的 quorum 配置项设定的 quorum 配置项设定的
    • 此时,这个哨兵可以再给其它哨兵发送命令,表明希望由自己来实现主从切换,并让所有其它哨兵投票,即「Leader 选举」
    • 在投票过程中,任何一个想成为 Leader 的哨兵,要满足两个条件:
      • 1.拿到半数以上的赞成票
      • 2.拿到的赞成票数同时还需要大于等于哨兵配置文件中的 quorum 值

09 | 切片集群:数据增多了,是该加内存还是加实例?

  • gt9:Redis Cluster 数据切片和实例的对应关系是怎样?
    • 采用哈希槽(Hash Slot),一个切片集群共有 16384 个哈希槽,每个键值对都会根据它的 key,映射到一个哈希槽中
      • 映射过程为:
      • 1.根据键值对的 key,按照 CRC16 算法计算一个 16bit 的值
      • 2.用这个 16 bit 值对 16384 取模,每个模数代表一个相应编号的哈希槽
    • 使用cluster create命令时,Redis 会自动把这些槽平均分布在集群实例上,可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数。但手动分配时,需要把 16384 个槽都分配完
  • gt9:客户端如何定位数据在切片集群的哪个实例上?
    • Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了
    • 客户端和集群实例建立连接后,实例会把哈希槽的分配信息发给客户端
    • 客户端收到哈希槽信息后,会把哈希槽信息缓存在本地
  • gt9:若集群中,实例和哈希槽的对应关系改变了,客户端如何感知?
    • Redis Cluster 提供了一种重定向机制,客户端把一个键值对的操作请求发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就会给客户端返回 MOVED 命令响应结果,其中包含新实例的访问地址

GET hello:key (error) MOVED 13320 172.16.19.5:6379```

  • gt9:如果数据只有一部分迁移到新实例,客户端请求重定向操作会怎样?
    • 收到一条 ASK 报错信息

GET hello:key (error) ASK 13320 172.16.19.5:6379``` - ASK 命令表示客户端请求的键值对所在的哈希槽 13320,在 172.16.19.5 这个实例上,但是这个哈希槽正在迁移 - 此时客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令,然后在发送 GET 命令以读取数据 - ASK 命令不会更新客户端缓存的哈希槽分配信息,即只让客户端能给新实例发送一次请求。MOVED 命令会更改本地缓存,让后续命令都发往新实例

Last Updated:
Contributors: Shiqi Lu