redis集群是redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能
节点
一个redis集群通常由多个节点组成,在刚开始的时候,每个节点都是相互独立的,它们都处于一个只包含自己的集群当中,可以通过在配置选项中指定cluster-enabled来在启动的时候将一台单独服务器开启服务器的集群模式,也可以在启动之后,通过CLUSTER MEET 命令来开启服务器的集群模式。
集群的数据结构保存在clusterNode结构里面,代码如下:
-
typedef struct clusterNode {
-
mstime_t ctime; /* Node object creation time. */
-
char name[CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
-
int flags; /* CLUSTER_NODE_... */
-
uint64_t configEpoch; /* Last configEpoch observed for this node */
-
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
-
int numslots; /* Number of slots handled by this node */
-
int numslaves; /* Number of slave nodes, if this is a master */
-
struct clusterNode **slaves; /* pointers to slave nodes */
-
struct clusterNode *slaveof; /* pointer to the master node. Note that it
-
may be NULL even if the node is a slave
-
if we don't have the master node in our
-
tables. */
-
mstime_t ping_sent; /* Unix time we sent latest ping */
-
mstime_t pong_received; /* Unix time we received the pong */
-
mstime_t fail_time; /* Unix time when FAIL flag was set */
-
mstime_t voted_time; /* Last time we voted for a slave of this master */
-
mstime_t repl_offset_time; /* Unix time we received offset for this node */
-
mstime_t orphaned_time; /* Starting time of orphaned master condition */
-
long long repl_offset; /* Last known repl offset for this node. */
-
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
-
int port; /* Latest known clients port of this node */
-
int cport; /* Latest known cluster port of this node. */
-
clusterLink *link; /* TCP/IP link with this node */
-
list *fail_reports; /* List of nodes signaling this as failing */
-
} clusterNode;
每个节点都会使用一个clusterNode结构来记录自己的状态,并为集群中的所有其他节点都创建一个相应的clusterNode结构,以此来记录其他节点状态;
其中的link字段是一个clusterLink结构,该结构保存了连接节点所需的有关信息,
-
typedef struct clusterLink {
-
mstime_t ctime; /* Link creation time */
-
int fd; /* TCP socket file descriptor */
-
sds sndbuf; /* Packet send buffer */
-
sds rcvbuf; /* Packet reception buffer */
-
struct clusterNode *node; /* Node related to this link if any, or NULL */
-
} clusterLink;
最后,每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所处的状态
-
typedef struct clusterState {
-
clusterNode *myself; /* This node */
-
uint64_t currentEpoch;
-
int state; /* CLUSTER_OK, CLUSTER_FAIL, ... */
-
int size; /* Num of master nodes with at least one slot */
-
dict *nodes; /* Hash table of name -> clusterNode structures 集群中其他节点信息*/
-
dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
-
clusterNode *migrating_slots_to[CLUSTER_SLOTS];
-
clusterNode *importing_slots_from[CLUSTER_SLOTS];
-
clusterNode *slots[CLUSTER_SLOTS];
-
uint64_t slots_keys_count[CLUSTER_SLOTS];
-
rax *slots_to_keys;
-
/* The following fields are used to take the slave state on elections. */
-
mstime_t failover_auth_time; /* Time of previous or next election. */
-
int failover_auth_count; /* Number of votes received so far. */
-
int failover_auth_sent; /* True if we already asked for votes. */
-
int failover_auth_rank; /* This slave rank for current auth request. */
-
uint64_t failover_auth_epoch; /* Epoch of the current election. */
-
int cant_failover_reason; /* Why a slave is currently not able to
-
failover. See the CANT_FAILOVER_* macros. */
-
/* Manual failover state in common. */
-
mstime_t mf_end; /* Manual failover time limit (ms unixtime).
-
It is zero if there is no MF in progress. */
-
/* Manual failover state of master. */
-
clusterNode *mf_slave; /* Slave performing the manual failover. */
-
/* Manual failover state of slave. */
-
long long mf_master_offset; /* Master offset the slave needs to start MF
-
or zero if stil not received. */
-
int mf_can_start; /* If non-zero signal that the manual failover
-
can start requesting masters vote. */
-
/* The followign fields are used by masters to take state on elections. */
-
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
-
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
-
/* Messages received and sent by type. */
-
long long stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT];
-
long long stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT];
-
long long stats_pfail_nodes; /* Number of nodes in PFAIL status,
-
excluding nodes without address. */
-
} clusterState;
CLUSTER MEET命令的实现模式跟tcp的三次握手有点像,来回发PING,PONG命令,然后在成功的情况下各自节点都为对方创建一个clusterNode结构。初次握手完成之后,发送CLUSTER MEET的节点会通过gossip协议将接收命令的节点信息传送给集群内其他的节点,然后其他节点与接收命令的节点握手,最终达成一致状态
槽指派
redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分成了16384个槽,数据库中的每个键都属于这16384个槽中的一个,集群中的每个节点可以处理0个或最多16384个槽
当数据库中的16384个槽都有节点在处理时,集群处于上线状态;如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态。
通过向加点发送CLUSTER ADDSLOTS [slot ...] 命令,我们可以将一个或多个槽指派给节点负责,clusterNode结构的slots(二进制位数组)属性和numbslot属性记录了节点负责哪些槽。
一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。而clusterState结构中的slots数组记录了集群中所有16384个槽的指派信息,每个槽只能属于一个clusterNode所处理
在集群中执行命令
对所有的槽都进行了指派之后,集群就会进入上线状态,这时,客户端就可以向集群中的节点发送数据命令了。当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:如果键所在的槽正好就指派给了当前节点,那么节点直接执行;如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED :错误,指引客户端转向正确的节点
节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机redis没有这一限制。另外,除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的slots_to_keys跳跃表来保存槽和键之间的关系,跳跃表每个节点的分值是一个槽号,而每个节点的成员都是一个数据库键,这个结构会随着数据库键的变化而变化
重新分片
redis集群的重新分片操作可以将任意数量已经指派给某个节点的槽改为指派给另一个节点,并且相关槽所属的键值对也会从源节点移动到目标节点。
重新分片的操作是由redis的集群管理软件redis-trib负责执行的,其对集群的单个槽slot进行重新分片的步骤如下:
1. redis-trib对目标节点发送CLUSTER SETSLOT IMPORTING 命令,让目标节点准备好从源节点导入属于槽slot的键值对,底层由clusterState 结构当中的importing_slots_from字段表示
2. redis-trib对源节点发送 CLUSTER SETSLOG MIGRATING 命令,让源节点准备好将属于槽slot的键值对迁移至目标节点,底层由clusterState结构当中的migrating_slots_to字段表示
3. redis-trib向源节点发送CLUSTER GETKEYSINSLOT 命令,获得最多count个属于槽slot的键值对的键名
4. 对于步骤3获得的每个键名,redis-trib都向源节点发送一个MIGRATE 0 命令,将被选中的键原子的从源节点迁移至目标节点
5. 重复执行步骤3和步骤4,直到源节点中保存的所有属于槽slot的键值对都被迁移到目标节点位置
6. redis-trib向集群中的任意一个节点发送CLUSTER SETSLOT NODE 命令,将槽slot指派给指定目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道slot已经指派给了目标节点
在迁移的过程中,当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就位于正在被迁移的槽时:如果源节点有,直接返回,如果没有,返回ASK错误,让客户端去目标节点操作,个人觉得这个跟redis的rehash比较像
阅读(6717) | 评论(0) | 转发(0) |