1、复制
用SLAVEOF命令让一个服务器(从)去复制另一个服务器(主)。例如127.0.0.1:12345> SLAVEOF 127.0.0.1 6379,127.0.0.1:12345将成为127.0.0.1:6379的从服务器。
(1)旧版复制功能实现,分为同步和命令传播两个操作。当执行SLAVEOF时,首先执行同步操作,【1】从服务器向主服务器发送SYNC命令【2】主服务器执行BGSAVE,生成RDB文件,并用一个缓冲区记录新的写命令【3】将RDB文件发送给从服务器,从服务器载入RDB文件【4】将缓冲区的写命令发送给从服务器并执行。命令传播就是主服务器将自己执行的写命令发送给从服务器执行。旧版复制功能的缺陷是从服务器断线重连后,需要重传RDB文件。
(2)新版复制功能的实现,用PSYNC命令代替SYNC执行复制时的同步操作,有完整重同步和部分重同步两种模式。完整重同步用于初次复制,和SYNC一样;部分重同步用于断线重连后的复制。主从服务器各自维护一个复制偏移量来实现部分重同步,主服务器发送N字节、从服务器接受N字节都会将复制偏移量+N。
复制积压缓冲区,是由主服务器维护的固定长度的队列,默认1 MB,保存最近的写命令。如果从服务器断线中产生的数据大于缓冲区大小,就进行完整重同步。
除了复制偏移量、复制积压缓冲区外,实现部分重同步还需要服务器运行ID。是在服务器启动时生成,从服务器初次复制时,保存主服务器ID,重连时判断ID是否相等,即根据ID判断是不是一次重连,是执行部分重同步还是完整重同步。
(3)复制的步骤
【1】设置主服务器的地址和端口,例如SLAVEOF 127.0.0.1 6379,将127.0.0.1和6379保存在从服务器的masterhost和masterport属性里。SLAVEOF是一个异步命令,设置完成后从服务器返回OK,然后再执行复制工作
struct redisServer{
// 主服务器地址
char *masterhost;
// 主服务器端口
int masterport;
};
【2】建立套接字连接【3】发送PING【4】身份验证【5】从服务器发送自己的监听端口,保存在redisClient里。从服务器作为一个redisClient保存在主服务器的redisServer.clients里【6】同步【7】命令传播
【8】心跳检测:检测主从服务器的网络连接,辅助实现min-slaves选项(防止主服务器在不安全的情况下写),检测命令丢失;
2、Sentinel
Sentinel(哨岗、哨兵)是Redis高可用性的解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视多个主服务器以及下属的从服务器,并在监视的主服务器下线时,自动将下线的主服务器的某个从服务器升级为新的主服务器,旧的主服务器上线时成为从服务器。
Sentinel本质上是运行在特殊模式下的Redis服务器,端口和命令表等不同。初始化Sentinel时会创建sentinelState,其中有一个master字典保存了所有被Sentinel监视的主服务器,键是主服务器名字,值是sentinelRedisInstance结构,表示被Sentinel监视的Redis服务器实例,可以是主服务器、从服务器或者另一个Sentinel。
Sentinel会创建两个连向被监视主/从服务器的异步网络连接,一个是命令连接,一个是订阅连接。Sentinel默认10 s一次向被监视的主服务器发送INFO命令,通过回复获取主服务器的当前信息(包括各个主服务器的slaves)。
Sentinel可以向主从服务器发送信息,可以接受来自主从服务器的频道信息。一个Sentinel可以分析接收到的频道信息来获知其他Sentinel的存在,所以用户在使用Sentinel时不需要提供各个Sentinel的地址信息,监视同一个服务器的多个Sentinel可以自动发现对方。Sentinel连接Sentinel是只创建命令连接。
Sentinel默认每秒一次向与他创建了命令连接的主从服务器、Sentinel发送PING命令,判断它们是否在线。为了确认主服务器是否下线,它会向监视这一主服务器的其他Sentinel询问(每个Sentinel设置的PING响应时间不一样),如果接收到足够的下线判断,进行故障转移。故障转移由领头Sentinel执行,每个发现服务器下线的Sentinel都会要求其他Sentinel将自己设置为领头Sentinel,先到先得,超过半数则是领头,否则重新选举。故障转移选出新的主服务器时,选出复制偏移量最大的从服务器作为新的主服务器。
3、集群
(1)节点
连接各个节点的工作通过CLUSTER MEET <ip> <port>命令完成,A和B握手后,A会将B的信息通过Gossip协议传播给A所在集群中其他节点,让其他节点也与B握手;
集群模式下serverCron会调用集群模式特有的clusterCron函数;
// 集群的节点
struct clusterNode{
// 创建节点的时间
mstime_t ctime;
// 节点名字
char name[REDIS_CLUSTER_NAMELEN];
// 节点状态
int flags;
// 配置纪元,用于故障转移
uint64_t configEpoch;
// 节点IP
char ip[REDIS_IP_STR_LEN];
// 节点端口号
int port;
// 连接的信息
clusterlink *link;
};
// clusterNode的link属性
typedef struct clusterlink{
// 连接的创建时间
mstime_t ctime;
// 套接字描述符
int fd;
// 输出缓冲区
sds sndbuf;
// 输入缓冲区
sds rcvbuf;
// 与这个连接关联的节点
struct clusterNode *node;
} clusterlink;
每个节点还保存一个clusterState结构,即每个节点都保存着集群的状态;
typedef struct clusterState{
clusterNode *myself;
// 配置纪元
uint64_t currentEpoch;
// 集群状态,在线/下线
int state;
// 集群中至少处理着一个槽的节点数量
int size;
// 集群节点名单,Map<name,clusterNode>
dict *nodes;
} clusterState;
(2)槽指派
Redis集群通过分片保存键值对,集群的整个数据库分成16384(2^14)个槽,有任何一个槽没被节点处理,则集群处于下线状态。通过向节点发送CLUSTER ADDSLOTS 0 1 2 3 …… 100命令,可以将一个或多个槽指派给节点负责。一个节点除了会将自己负责的槽记录在clusterNode的slots里,还会发送消息告知集群其他节点;
struct clusterNode{
// 用bitmap保存节点的槽
unsigned char slots[16384/8];
int numslots;
};
clusterState中的slots数组记录了集群所有槽的指派信息,而clusterNode.slots数组只记录了一个节点的槽指派;
typedef struct clusterState{
// 记录了每个槽由哪个clusterNode管理
clusterNode *slots[16384];
} clusterState;
节点用键的CRC-16校验和计算槽号,CRC16(key) & 16383;客户端向节点发送命令时,如果键所在槽没有指派给当前节点,返回MOVED错误,指引客户端重定向到正确节点;
集群节点和单机数据库类似,也保存键值对字典和过期字典,节点只能使用0号数据库。节点还会用clusterState中的slots_to_keys跳表来保存槽与键的关系,保存哪个键在哪个槽里,分值是槽号,可以快速获得同一个槽里的若干个键;
typedef struct clusterState{
zskiplist *slots_to_keys;
} clusterState;
(3)重新分片
CLUSTER SETSLOT <slot> IMPORTING <source_id>命令从源节点导入槽slot;
CLUSTER SETSLOT <slot> MIGRATING <target_id>命令导出槽slot到目标节点;
typedef struct clusterState{
// 当前节点正在从其他节点导入的槽
clusterNode *importing_slots_from[16384];
// 当前节点正在迁移的槽
clusterNode *migrating_slots_to[16384];
} clusterState;
重新分片时会产生ASK错误,即由源节点负责的K-V转移到了目标节点里,需要重定向。和MOVED不同,MOVED表示槽的负责权转移了,ASK只是重新分片时的一种临时措施;
(4)复制和故障转移
集群中可以用CLUSTER REPLICATE <node_id>让节点成为node_id的从节点,并开始对主节点复制,并告知集群其他节点;
每个节点定期向其他节点发送PING,如果一个主节点被半数以上主节点标记为下线,则标记为已下线。从它的从节点选举出新的主节点,和领头Sentinel的选举类似,先到先得,超过半数。然后执行SLAVE no one,成为新的主节点,负责处理槽,告知其他节点;
(5)消息
节点间主要有MEET、PING、PONG、FAIL、PUBLISH五种消息;