多机基本概念 与 主从复制
# 主从服务器
单机 Redis 在机器故障后存在较高的数据丢失风险,以及重启时数据恢复较慢,导致可用性过低。
以及内存存在瓶颈,或者硬件跟不上。
于是可以使用多台服务器互相连通保存互相的副本,做冗余备份,同时保证数据同步。
- 提供数据方:主服务器
master
,只写数据 - 接收数据方:从服务器
slave
,只读数据
这里 master 要和 slave 进行同步数据,所以核心工作是同步。
它们之间的关系是:
master:
- slave
- slave
- slave
...
2
3
4
5
一个 slave 只对应一个 master,但一个 master 可以对应多个 slave
# 主从复制
# 作用
- 负载均衡:基于master写slave读的读写分离,与主从结构,分摊 master 的负载,提高服务的读写负载能力与服务器并发量和吞吐量。
- 故障恢复:若master出了问题就用slave提供临时服务,可实现快速的故障恢复。
- 数据冗余:多台主机多个数据热备份,是一种数据冗余方式。
- 高可用:基于主从复制添加哨兵和集群,实现服务器高可用。
# 流程1.建立连接
流程编号 | master | slave |
---|---|---|
1 | 发送指令:slaveof ip port | |
2 | 接收到指令,进行响应 | |
3 | 保存 masterhost 与 masterport | |
4 | 根据保存的连接信息创建 socket 与 master 进行通信 | |
5 | 周期性发送 ping 检测是否连通 | |
6 | 接收到 ping ,响应 pong | |
7 | 将监听自己的端口发送给 master:replconf listening-port <port-number> | |
8 | 保存 slave 的端口号 |
现在我们做一下建立连接的示范,我们令 127.0.0.1:6379
做为 master,127.0.0.1:6380
作为 slave。
首先看一下 config/redis-6379.conf 文件:
bind 127.0.0.1 ::1
port 6379
daemonize no
# logfile "6379.log"
dir /etc/redis/data
dbfilename dump-6379.rdb
rdbcompression yes
rdbchecksum yes
appendonly yes
appendfsync everysec
appendfilename appendonly-6379.aof
2
3
4
5
6
7
8
9
10
11
12
13
至于 redis-6380.conf
文件只需要将这里的 6379 全部换成 6380 ,同时加上一行 slaveof 127.0.0.1 6379
。
下面演示由于要用四宫格,于是全部以贴图进行,我们这里介绍一下成员。
然后两个 redis-server 都打开,会弹出这样的日志
其中红框内的就是 master-slave 连接信息的日志
然后是两个客户端,将它们分别启动后 master 调用 set name snopzyz
后观察 slave 调用 get name
的状态:
发现这里也是成功获取到 master 的 key。
分别键入 info
,我们也可以看到各自已经生成了连接的信息。
若需要断开,则在 slave.redis-cli 中敲入:slaveof no one
即可
# 流程2.数据同步
首先了解一下什么是全量复制和部分复制:
- 全量复制产生于连接开始,slave 需要将 master 中的数据通过 rdb 的形式全部刷过来
- 部分复制产生于因为某种情况无法同步数据(全量复制、断网...)的一段时间后,slave 通过刷取 master 提供的复制积压缓冲区内的数据进行复制(有点类似 aof)
其中全量复制就很暴力简单,而部分复制就需要谈谈了,它的核心属性有以下几种:
- 两台服务器的运行 id:存放在
info
.#Server.run_id 下,是一个 位的数字 - 复制积压缓冲区:用于避免某个 slave 断网后重连不知道自己漏掉了哪些命令
- 结构:一个队列,每个元素为 {偏移量,字节值} 对
- 字节值-命令:分解保存指令的最原始状态
.. $3\r\nset\r\n$4\r ..
- 偏移量-进度:
- slave 的 offset 变量记录自己复制到哪一个偏移量了
- master 的 offset 变量记录自己上传到哪一个偏移量了
介绍完这两个下面就是数据同步的流程了
总览 | 流程编号 | master | slave |
---|---|---|---|
全量复制 | 1 | 发送指令 psync2 ? -1 | |
2 | 记录当前复制偏移量,执行 bgsave | ||
3 | 第一个 slave 连接时创建复制积压缓冲区 | ||
4 | 生成 rdb 发送 +FULLRESYNC runid offset 再通过 socket 将 rdb 发送给 slave | ||
5 | 收到 +FULLRESYNC 保存 master 的 runid 和 offset 接收 rdb,清空本机数据后通过 rdb 进行数据恢复 | ||
部分复制 | 6 | 向 master 发送 rdb 恢复完成的消息 发送之前保存的属性 psync2 runid offset ,进行复制机制判断 | |
7 |
接受命令,判断 runid 和 offset
| ||
8 | 收到 +CONTINUE 保存 master 的 offset 接收信息后执行 bgrewriteaof 恢复数据 |
这还是在两个 server 启动时可以看到,我们关注下面的日志信息。
这里仔细阅读一下英文,可以发现这段就是全量复制和部分复制的过程。
注意事项
- 如果 master 数据量非常大那么应避开流量高峰期,不然容易导致 master 阻塞
- 全量同步过程中该缓冲区被塞满使得反复进行全量复制的问题
- 定义复制缓冲区的空间:
repl-backlog-size 1mb
- 在全量/部分复制时关闭对外服务:
slave-serve-stale-data yes|no
- 定义复制缓冲区的空间:
- 多个 slave 向 master 请求同步的时机应当错开
- 多层 slave-master 由于数据同步延迟可能会导致数据一致性变差
# 流程3.命令传播(反复同步)
实时保持数据同步。
异常:若此阶段发生断网:
- 闪断闪连:忽略
- 短时间中断:部分复制
- 长时间中断:全量复制
心跳
主要功能是判断对方是否在线,次要功能是获取一些连接信息。
master 心跳:发送一次 PING
, 以 repl-ping-slave-period
确定时间间隔,默认 秒,主要用于判断 slave 是否在线
slave 心跳:一秒发送一次 REPLCONF ACK {offset}
汇报自己的复制偏移量以获取最新数据,同时检测 master 是否在线
通过心跳我们可以获取到实时的 slave 连接数量和网络延时,若掉线过多或者延时过高,master 将拒绝所有信息同步,标准由我们自己配置。
min-slaves-to-write 2
:最少连接的 slave 数min-slaves-max-lag
:所有 slave 都达到的最长的延时(和我们主机调取info
获取的 ”每个 slave 的lag
也就是 slave 最后一次连接的时间间隔“ 配对)
流程编号 | master | slave |
---|---|---|
1 | 发送命令 ping | 发送命令 replconf ack offset ,进行复制机制判断 |
2 |
接收 slave 命令,判断 offset 是否在缓冲区中
| 接收 master 命令,返回 pong |
3 | 收到 +CONTINUE 保存 master 的 offset 接收信息后执行 bgrewriteaof 恢复数据重新进入流程1 |
# 问题与优化
# 发生频繁全量复制
# 情况1:master 重启变更 runid
master 数据量后面会特别大,此时如果 master 重启了 runid 将会变化,导致所有的 slave 都得再次执行一次全量复制。
优化方案:
在 master 内创建 master-replid
变量,用同等于 runid 的策略生成但是长度为 位,发送给所有的 slave。
master 关闭时进行 RDB 持久化,把 runid 作为 repl-id
保存,offset 作为 repl-offset
保存,这样在重启之后会续用上次的 runid 和 offset,让所有的 slave 认为还是之前的 master。
# 情况2:复制缓冲区过小
网络环境不佳,使得 slave 不提供服务,主要原因是复制缓冲区过小,slave 每次的 offset 都越界从而反复全量复制。
优化方案:
修改复制积压缓冲区缓冲区大小:repl-backlog-size
值建议为:
second
= master 到 slave 的重连平均时长
write_size_per_second
= master 平均每秒写命令数据总量
repl-backlog-size
= second
write_size_per_second
# 发生频繁网络中断
# 情况1:slave 接收慢查询影响心跳
因为 slave 每秒都会发送 REPLCONF ACK
,但如果 slave 正在接受像 keys *
这样的慢查询的话,master 的复制定时函数 replicationCron()
会发现 slave 长时间没有响应。然后就会将 slave 断开再重连再断开...
优化方案:
调整合理的超时时间 repl-timeout
,默认是 60 秒,可以调整一下。
# 情况2:slave ping密度低影响心跳
若 slave 发送 ping 的频度低,且网络中存在丢包,则会导致 master 将该 slave 认为是超时将其断开。
优化方案:
提高 ping 的发送频度 repl-ping-slave-period
(建议让上面的超时时间 repl-timeout
是该发送频度的 倍)
# 数据不一致
# 多个 slave 获取相同数据不同步
网络信息不同步导致的数据发送延迟,使得多个 slave 的数据有短暂的不一致。
优化方案:
- 尽量在一个机房部署集群。
- 若某个 slave 延迟过大就屏蔽它
slave-server-stale-data yes|no
(慎用,除非是数据一致性要求很高)。