一、节点
最开始时,每个Redis实例自己是一个集群,连接各个节点的工作通过CLUSTER MEET 命令完成,A和B握手后,A会将B的信息通过Gossip协议传播给A所在集群中其他节点,让其他节点也与B握手;
// 集群的节点
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;
二、槽指派
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;
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽(Slot),集群的每个节点负责一部分hash槽。客户端向节点发送命令时,如果键所在槽没有指派给当前节点,返回MOVED错误,指引客户端重定向到正确节点;
集群节点和单机数据库类似,也保存键值对字典和过期字典,节点只能使用0号数据库。节点还会用clusterState中的slots_to_keys跳表来保存槽与键的关系,保存哪个键在哪个槽里,分值是槽号,可以快速获得同一个槽里的若干个键;
typedef struct clusterState{
zskiplist *slots_to_keys;
} clusterState;
三、重新分片
CLUSTER SETSLOT IMPORTING <source_id>命令从源节点导入槽slot;
CLUSTER SETSLOT MIGRATING <target_id>命令导出槽slot到目标节点;
typedef struct clusterState{
// 当前节点正在从其他节点导入的槽
clusterNode *importing_slots_from[16384];
// 当前节点正在迁移的槽
clusterNode *migrating_slots_to[16384];
} clusterState;
重新分片时会产生ASK错误,即由源节点负责的K-V转移到了目标节点里,需要重定向。和MOVED不同,MOVED表示槽的负责权转移了,ASK只是重新分片时的一种临时措施;
四、复制和故障转移
集群中可以用CLUSTER REPLICATE <node_id>让节点成为node_id的从节点,并开始对主节点复制,并告知集群其他节点;
每个节点定期向其他节点发送PING,如果一个主节点被半数以上主节点标记为下线,则标记为已下线。从它的从节点选举出新的主节点,和领头Sentinel的选举类似,先到先得,超过半数。然后执行SLAVE no one,成为新的主节点,负责处理槽,告知其他节点;
五、消息
节点间主要有MEET、PING、PONG、FAIL(下线)、PUBLISH(群发,客户端给某个节点发消息,这个节点再给其他节点发,不是客户端直接群发)五种消息;