Redis 可以使用从属服务器来实现读写分离提高吞吐量或在主服务器故障时接替主服务器以提高可用性。
每个 Redis 服务器实例都可以配置多个 slave 节点,slave 服务器也可以拥有次级 slave 节点, 可以组织成复杂的树状结构(虽说生产环境下极少有人这么做)。
配置主从复制
为了尝试配置主从复制,我们至少需要两个 redis 服务器实例。最简单的方法是在 redis 官网下载 redis-server 二进制可执行文件,分别放在 master 和 slave 目录中。
在每个目录中分别创建 redis.conf 配置文件。master 实例的配置文件采用默认即可, 在 slave 实例中进行主从复制配置:
代码语言:javascript复制# 和主服务器 6379 区分
port 6380
# 主服务器 ip 端口
slaveof 127.0.0.1 6379
# 如果主服务器配置了密码请写在这个配置项中
# masterauth <master-password>
##
## 接下来的选项保留默认配置即可,这里仅做介绍
##
# 当与 master 断开连接或正在进行同步时
# yes: 仍然正常响应客户端请求,但可能返回过时数据
# no: 除 INFO 和 SLAVEOF 命令正常外,其它命令均返回 SYNC with master in progress 错误
slave-serve-stale-data yes
# 从服务器只读
slave-read-only yes
使用redis-server redis.conf
命令分别启动 redis-server 实例。 使用redis-cli -p 6380
命令连接从服务器:
127.0.0.1:6380> info Replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:16
master_sync_in_progress:0
slave_repl_offset:3640
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:0b4e100aa9e9fda54aeba2bc110316d811ac2ff6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3640
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3640
127.0.0.1:6380> get a
1
127.0.0.1:6380> set a 2
(error) READONLY You can't write against a read only slave.
SLAVEOF host port
可以动态改变从服务器所属的 master 节点。SLAVEOF NO ONE
关闭复制功能,并从 slave 服务器转变回 master 服务器,原来同步所得的数据集不会被丢弃。在当主服务器故障时,可以使用SLAVEOF NO ONE
命令将 slave 服务器提升为 master。
主从复制原理
SYNC
在 Redis 2.8 之前的版本里,Redis 仅支持全量复制不支持增量复制,这极大的影响了主从同步的性能。
Redis 2.8 之前的版本主从复制流程如下:
- slave 发送 SYNC 命令给 master
- master 执行 bgsave 命令生成 rdb 文件。于此同时,所有新的写命令都将被写入复制缓冲区内
- master 将 rdb 文件发送给 slave
- master 将缓冲区中的命令同步给 slave, 完成一次主从同步
PSYNC
Redis 2.8 开始使用 PSYNC 命令代替 SYNC 命令, PSYNC 具有全量复制和增量复制功能。
master 和 slave 节点均拥有一个 runid 作为自己的唯一标识。
master 和 slave 会各自维护一个复制偏移量,在增量复制时标识同步进度。
master 会维护一个 FIFO 的复制缓冲区(replication backlog),默认大小 1mb。
代码语言:javascript复制# 复制缓冲区大小
repl-backlog-size 1mb
# 当 master 不再与任何 slave 保持连接时,复制缓冲区可能被清空
# repl-backlog-ttl 用于配置从断开连接到清空缓冲区间隔的秒数
# 0 表示永不清除缓冲区
repl-backlog-ttl 3600
接下来我们可以开始说明 PSYNC 命令执行的流程:
- slave 向 master 请求同步
- 若 slave 未与任何 master 同步过或执行了 SLAVEOF NO ONE 命令,则向 master 发送
PSYNC ? -1
命令要求进行全量同步。 - 否则,则向 master 发送
psync <runid> <offset>
命令要求增量同步,其中 runid 是上次同步的主服务器的ID,offset 是同步偏移量
- 若 slave 未与任何 master 同步过或执行了 SLAVEOF NO ONE 命令,则向 master 发送
- master 响应同步请求
- 若 slave 请求增量同步且满足:1. runid 与自身相同;2. 同步偏移量处于自身复制缓冲区内,则响应
continue
将复制缓冲区内的数据同步到 slave - 若 slave 请求增量同步但不同时满足上述两个条件或 slave 请求全量同步, 则响应
fullresync <runid> <offset>
执行全量同步,其中 runid 是自身ID, offset 是自身同步偏移量。
- 若 slave 请求增量同步且满足:1. runid 与自身相同;2. 同步偏移量处于自身复制缓冲区内,则响应
- 若自身版本过低不支持
PSYNC
命令则响应 error, slave 会尝试使用 SYNC 命令进行同步。
哨兵
简单的主从复制架构在 master 故障后会不可用,Redis 官方提供了哨兵(sentinel)机制自动实现主备切换保证高可用。
哨兵机制通过一组哨兵节点监控主从节点的运行状态,并在主节点故障后选举新的主节点。
哨兵节点定时执行3个任务:
- 哨兵节点每隔10s向主从节点发送
INFO
命令以更新拓扑图,自动感知新的 slave 节点。 - 哨兵节点每隔1s向主从节点发送
PING
命令进行心跳检测。 - 哨兵节点每隔2s向
__sentinel__:hello
频道发送自身哨兵节点信息和自身了解的 master 信息。所有哨兵节点均会订阅该频道,并以此更新哨兵集群信息。
若哨兵节点发现 master 节点心跳响应超时,则认为 master 主观下线。此时,master 可能真的已经崩溃也可能仅仅是此哨兵节点与 master 之间出现网络故障。
认为 master 主观下线的哨兵会向其它哨兵发送sentinel is-master-down-by addr
询问 master 是否下线。若半数以上的哨兵认为 master 已经下线则认为 master 客观下线。
哨兵节点会选举自己第一个收到的 is-master-down-by 命令的发送者为哨兵领导者。若某一个节点得到过半投票则会成为哨兵领导者,若没有节点得到半数以上票则会进入下一轮投票。此选举流程与 Paxos 算法类似。
哨兵领导者负责选择一个slave节点提升为新 master 节点, 选择逻辑为:
- 过滤掉不健康的 slave 节点
- 选择
slave-priority
配置值最小的从节点。若有多个从节点 slave-priority 最小且相同则进入下一步 - 选择复制偏移量最大的从节点,这意味着这个从节点上面的数据最完整。若仍有多个 slave 节点偏移量相同则进入下一步
- 选择 runid 最小的从节点
新的 master 节点选出后会执行提升流程:
- 向新选出的 master 节点发出 SLAVEOF NO ONE 命令,提升为新的 master 节点
- 向其它 slave 节点发出 SLAVEOF 命令跟随新的 master 节点
- 在哨兵集群中将下线的 master 节点更新为下线的 slave 节点,在其回复后命令其跟随新的 master