这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战
一、概述
即使用了
AOF
和RDB
,单机Redis
也依然存在服务不可用的问题。
例如:一个Redis
实例宕机了,之后在恢复期间,是无法服务新来的数据。
Redis
具有高可靠性,意味着:
- 数据尽量少丢失:
AOF
和RDB
保证了。 - 服务尽量少中断:增加副本冗余量。
即将一份数据同时保存在多个实例上。
即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。
单机的 redis
几乎不太可能 QPS
超过 10w。
要提高并发,一般的方案是 读写分离。
多实例保存同一份数据,就会存在多实例(副本)之间的数据如何保持一致性问题?
Redis
提供了主从模式,以保证数据副本的一致,主从库之间采用读写分离方式:
- 读操作:主库、从库读可以接收。(主要是从库)
- 写操作:首先到主库执行,之后主库同步给从库
那么问题来了:主从库同步是如何完成的呢?
主从库同步
先来了解下,Redis
主从库同步三种模式:
- 全量复制
- 基于长连接的命令传播
- 增量复制
启动多个 Redis
实例,可以通过 replicaof
(Redis 5.0
之前使用 slaveof
)命令来形成主库和从库关系。
举个栗子:
- 实例1 (
ip
:172.16.19.3
) - 实例2 (
ip
:172.16.19.5
)
在实例2上执行 replicaof
命令,使实例2变为实例1的从库:
1 | shell复制代码replicaof 172.16.19.3 6379 |
主从库数据同步的三个阶段,如下:
- 第一阶段:建立连接,协商同步
- 第二阶段:主库同步数据给从库
- 第三阶段:主库发送新写命令给从库
第一阶段:建立连接,协商同步
第一阶段:是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。
- 从库发送命令
psync
:
从库会发送psync
命令(表示需要进行数据同步)给主库,主库会根据命令的参数来进行复制。psync
命令包含了主库的runID
和 复制进度offset
:
runID
:是每个Redis
实例启动时都会自动生成的一个随机ID
,用来唯一标记这个实例。
当从库和主库第一次复制时,因为不知道主库的
runID
,所以将runID
设为“?”。
offset
:此时设为 -1,表示第一次复制。
- 主库接收
psync
命令后:会用FULLRESYNC
命令进行响应
- 主库会执行
bgsave
命令,生成对应的RDB
文件。
FULLRESYNC
响应表示第一次复制采用的全量复制。
参数:主库runID
和 主库目前的复制进度offset
第二阶段:主库同步数据给从库
- 从库:
从库接收到RDB
文件后,会先清空当前数据库,然后加载RDB
文件。
因为是第一次全量复制,所以从库里之前的数据都是脏数据。
- 主库:
主库会在内存中用专门的replication buffer
,记录RDB
文件生成后收到的所有写操作。
全量复制只是当前的时刻主库的全量,之后会有增量同步,所以需要记录入每个从库的进度。
第三阶段:主库发送新写命令给从库
这个阶段是增量同步。
当主从库完成了全量复制,它们之间就会一直维护一个网络连接:避免频繁建立连接。
- 主库:
同步之后的写命令给从库。
- 发送写命令
- 同时会在
replication buffer
中修改对应操作
- 从库:
从库只需接收命令,在本地内存中执行。
二、问题
(1)集群过大:主从级联模式
当集群过大后,影响主库主要操作有:
fork
操作:如果从库数量过多,且都要和主库进行全量复制,就会导致主库忙于fork
子进程进行RDB
文件。
fork
操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。
- 传输
RDB
文件:占用主库的网络带宽
为了分担主库压力:可以采用 主 - 从 - 从
模式。
说白了,把从库假装成主库,在其下在进行关联子从库。
1 | shell复制代码# 命令操作类似: |
(2)主从库断连会怎样?断连重连又是如何?
1)主从断连:可能会造成数据不一致
当主从库断连后:
- 主库会把断连期间收到的写操作命令,写入
replication buffer
:存储写命令 - 同时也会把这些操作命令写入
repl_backlog_buffer
缓冲区:存储记录
repl_backlog_buffer
是一个环形缓存区:
- 主库会记录自己写道的位置
- 从库则会记录自己已经读到的位置
repl_backlog_buffer
是一个环形缓冲区,当写满之后,会覆盖之前写入的操作。
如果从库的读取速度慢,就可能导致从库还未读取,主库已经开始覆盖写入了。这就会造成主从库间的数据不一致。
为避免这一情况,需要根据实际环境计算出合理的缓存区大小:
- 缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。
- 在实际应用中,需要考虑到可能存在一些突发的请求压力,通常需要把这个缓冲空间扩大一倍,即
repl_backlog_size
= 缓冲空间大小 * 2。
举个栗子:
- 主库每秒写入 2000 个操作
- 每个操作大小为
2KB
- 网络每秒能传输 1000 个操作
这时候就有 1000 个操作需要缓冲起来,就至少需要 2MB
的缓冲区(repl_backlog_size
)。
为了应对可能的突发压力,可以把 repl_backlog_size
设置为 4MB
2)断连后又重连
大致流程如下:
断开重连,需要知道主从双方需要知道对方情况:
- 从库:我的状况是这样的
- 主库:我看你需要这样的
所以,主从库的连接恢复之后:
- 从库首先会给主库发送
psync
命令,并把自己当前的slave_repl_offset
发给主库 - 主库会判断自己的
master_repl_offset
和slave_repl_offset
之间的差距master_repl_offset
:主库写入的位置,偏移量slave_repl_offset
:从库已复制的偏移量
在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset
会大于 slave_repl_offset
。
此时,主库只用把 master_repl_offset
和 slave_repl_offset
之间的命令操作同步给从库就行。
本文转载自: 掘金