复制
复制功能的实现
-
同步
-
命令传播
-
断线重连
部分重同步的实现
- 复制偏移量
- 复制积压缓冲区
- 固定长度先进先出队列,默认大小为1MB
- 服务器运行 ID
PSYNC 命令的实现
身份验证
心跳检测
- 向主服务器发送 info replication 命令,lag 表示从服务器最后一次向主服务器发送 replconf ack 命令距离现在过了多少秒。如果超过 1s,说明主从服务器之间的连接出现了故障。
- 辅助实现 min-slaves 配置选项
- min-slaves-to-write 3
- min-slaves-max-lag 10
- 在从服务器的数量少于3个,或者三个从服务器的延迟都大于或等于10秒时,主服务器将拒绝执行写命令
- 检测命令丢失
Sentinel
启动 Sentinel 的命令
## 第一种
redis-sentinel /path/to/your/sentinel.conf
## 第二种
redis-server /path/to/your/sentinel.conf --sentinel
Sentinel 服务器启动过程
-
初始化服务器
- 本质上是运行在特殊模式下的 Redis 服务器
-
使用 Sentinel 专用代码
-
初始化 Sentinel 状态
struct sentinelState { dict *masters; ... }
-
初始化 Sentinel 状态的 masters 属性
-
typedef struct sentinelRedisInstance { int flags; sentinelAddr *addr; ... } sentinelRedisInstance; typedef struct sentinelAddr { char *ip; int port; } sentinelAddr;
-
-
创建连向主服务器的网络连接,对于每个主服务器,Sentinel 会创建两个异步网络连接
-
命令连接。向主服务器发送命令,并接收回复
-
订阅连接。订阅主服务器的 xx 频道
__sentinel__:hello
-
Sentinel 获取主服务器信息
- 默认每 10s 一次,发送 info 命令,通过分析 info 命令的回复来获取主服务器的当前信息
Sentinel 获取从服务器信息
向主服务器和从服务器发送信息
-
默认每 2s 一次通过命令连接向所有被监视的服务器发送命令:
publish __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
Sentinel 之间的连接
Sentinel 之间只会创建命令连接,因为 Sentinel 需要通过接受主服务器或者从服务器发来的频道信息来发现未知的 Sentinel,所以才需要建立订阅连接。
检查主观下线状态
- 默认每 1s 一次向所有与它创建了命令连接的实例发送 PING 命令,并通过实例返回的 PING 命令回复来判断实例是否在线。
- Sentinel 配置文件的 down-after-milliseconds 选项
- 多个 Sentinel 设置的主观下线时长可能不同
检查客观下线状态
-
Sentinel 使用
sentinel is-master-down-by-addr <ip> <port> <current_epoch> <runid>
命令询问其他 Sentinel 是否同意主服务器已下线
-
目标 Sentinel 向源 Sentinel 回复
<down_state> <leader_runid> <leader_epoch>
-
当认为主服务器已经进入下线状态的 Sentinel 的数量超过配置中 quorum 参数的值时,则进入客观下线状态
选举领头 Sentinel
- 每次进行领头 Sentinel 选举之后,不论是否成功,所有 Sentinel 配置纪元自增一次。
- 在一个配置纪元里面,所有 Sentinel 都有一次将某个 Sentinel 设置为局部领头的机会,一旦设置,不能再改。
- 每个发现主服务器进入客观下线的 Sentinel 都会要求其他 Sentinel 将自己设置为局部领头。
- 当源 Sentinel 向目标 Sentinel 发送 sentinel is-master-down-by-addr 命令,并且 runid 参数不是 * 而是源 Sentinel 运行 id 时,表示源 Sentinel 要求目标 Sentinel 将前者设置为后者的局部领头。
- 设置局部头领的规则是先到先得。
- 如果在给定时限内没有选举成功,将在一段时间后,再次选举。
故障转移
- 选出新的主服务器
- 修改从服务器的复制目标
- 将旧的主服务器变为从服务器
集群
命令
# 连接节点
cluster meet <ip> <port>
# 查看集合信息
cluster nodes
cluster info
数据结构
struct clusterNode {
// 保存连接节点所需的有关信息
clusterLink *link;
// 二进制位数组
unsigned char slots[16384/8];
int numslots;
// ...
}
struct clusterLink {
// 连接的创建时间
mstime_t ctime;
int fd;
sds sndbuf;
sds rcvbuf;
struct clusterNode *node;
}
struct clusterState {
clusterNode *myself;
// 集群当前的配置纪元,用于实现故障转移
uint64_t currentEpoch;
int state;
int size;
dict *nodes;
clusterNode *slots[16384];
// 保存槽和键之间的关系
zskiplist *slots_to_keys;
// ...
}
cluster meet 命令的实现
槽指派
-
集群的整个数据库被分为 16384 个槽
-
cluster addslots <slot> [slots ...]
-
在集群中执行命令
-
计算键属于哪个槽
def slot_number(key): return CRC16(key) & 16383
-
查看给定键属于哪个槽的命令
cluster keyslot <key>
重新分片的实现原理
-
重新分片操作是由 Redis 的集群管理软件 redis-trib 负责执行的
-
步骤
-
向目标节点发送命令,让目标节点准备好从源节点导入属于 slot 的键值对
cluster setslot <slot> importing <source_id>
-
向源节点发送命令,让源节点准备好将属于 slot 的键值对迁移至目标节点
cluster setslot <slot> migrating <target_id>
-
迁移键
-
向集群中任意一个节点发送命令,将 slot 指派给目标节点
cluster setslot <slot> node <target_id>
-
从节点
-
设置从节点
cluster replicate <node_id>