Redis是一个高可用、高性能、高可扩展性的基于内存也支持持久化存储的kv存储数据库,redis相比较于之前的kv存储memcached而言,不但支持的value类型大大增加,并且还支持数据的持久化,弥补了memcached的不能持久化的缺点,但是在3.0之前的redis并不支持集群功能,这也是redis在3.0之前不能被大量部署的一个原因,但是由于3.0以后的redis支持了集群功能,redis就开始大量的替代之前的memcached,今天我从源代码层次学习下redis是怎么实现集群功能的。
我看的源代码是redis-3.0源代码,可以在下边这个链接下载到。
http://download.redis.io/releases/redis-3.0.0-rc1.tar.gz
redis的集群并不是类似于HDFS之类的namenode和datanode之类的架构,而是采用改进的一致性哈希算法来对数据进行分片,平均的分配到每一个master节点上,每一个master节点都有相对应的slave节点来复制master节点的数据,以便master宕机的时候来选举成为master节点。大体的架构如下图所示:
采取一致性哈希算法,保证每一块数据映射在0-16384的区间之上,然后这个区间的一部分分给一个master来服务(当然不会分的这么简单)。每一个client访问的时候就会访问对应的master,可能有人想问client是怎么知道数据在哪一个master上的,其实client也不知道,client会访问一个master,然后master发现数据不在这个master节点上,那么master就会告诉client存放client想要的数据所在的master地址,然后client就会访问到正确的master了。
那么,redis集群是怎么搭建起来的呢,难道是几十上百台机器同时开机自动连接的吗?当然不是,当只有一台机器的时候,可以认为这是一个只有一台机器的集群,然后client登录master执行cluster meet <ip> <port>来把指定ip地址的机器加入到集群里边。这样,这个集群就拥有两台机器了。就这样,一台一台的添加,就实现了大规模的redis集群。
首先看看和集群有关的数据结构,这些都是集群实现的基础。
-
- struct clusterNode {
-
-
- mstime_t ctime;
-
-
- char name[REDIS_CLUSTER_NAMELEN];
-
-
- int flags;
-
-
- uint64_t configEpoch;
-
-
- unsigned char slots[REDIS_CLUSTER_SLOTS/8];
-
-
- int numslots;
-
-
- int numslaves;
-
-
- struct clusterNode **slaves;
-
-
- struct clusterNode *slaveof;
-
-
- mstime_t ping_sent;
-
-
- mstime_t pong_received;
-
-
- mstime_t fail_time;
-
-
- mstime_t voted_time;
-
-
- mstime_t repl_offset_time;
-
-
- long long repl_offset;
-
-
- char ip[REDIS_IP_STR_LEN];
-
-
- int port;
-
-
- clusterLink *link;
-
-
- list *fail_reports;
- };
然后是记录集群之间的连接的结构体
-
- typedef struct clusterLink {
-
-
- mstime_t ctime;
-
-
- int fd;
-
-
- sds sndbuf;
-
-
- sds rcvbuf;
-
-
- struct clusterNode *node;
-
- } clusterLink;
然后是记录集群状态的结构体,每一个节点都有一个这个结构体,用来表示当前集群的状态。
- typedef struct clusterState {
-
-
- clusterNode *myself;
-
-
- uint64_t currentEpoch;
-
-
- int state;
-
-
- int size;
-
-
-
- dict *nodes;
-
-
-
-
- dict *nodes_black_list;
-
-
-
-
- clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
-
-
-
-
- clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
-
-
-
- clusterNode *slots[REDIS_CLUSTER_SLOTS];
-
-
-
-
- zskiplist *slots_to_keys;
-
-
-
-
- mstime_t failover_auth_time;
-
-
- int failover_auth_count;
-
-
- int failover_auth_sent;
-
- int failover_auth_rank;
-
- uint64_t failover_auth_epoch;
-
-
-
-
- mstime_t mf_end;
-
-
- clusterNode *mf_slave;
-
-
- long long mf_master_offset;
-
- int mf_can_start;
-
-
-
-
-
- uint64_t lastVoteEpoch;
-
-
- int todo_before_sleep;
-
-
- long long stats_bus_messages_sent;
-
-
- long long stats_bus_messages_received;
- } clusterState;
基本的结构体都介绍完了,我们来看看集群的代码实现吧。
首先在看redis单节点的初始化代码,这是集群的第一步,首先启动单节点服务。
redis源代码里,redis.c文件里的main函数是redis-server的开始,由于我们只关心集群的实现代码,一些和集群关系不大的我就忽略了。
int main(int argc, char **argv) {
………………..
//说明用户指定了参数,我们需要检查用户是不是指定了配置文件
if (argc >= 2) {
……………………
//读取配置文件
loadServerConfig(configfile,options);
}
然后跳入loadServerConfig函数来进行字符串配置的解析。在loadServerConfig函数里有以下代码,如果配置项有cluster-enabled,我们就设置server.cluster_enabled为1,表示集群功能的开启。
else if (!strcasecmp(argv[0],"cluster-enabled") && argc ==2) {
if ((server.cluster_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'";goto loaderr;
}
接着在main函数里边,读取玩配置文件,执行initServer()函数,在initServer函数里边,
// 如果服务器以 cluster模式打开,那么初始化 cluster
if (server.cluster_enabled) clusterInit();
接着我们进入clusterInit函数,看看单机集群设置的初始化代码
-
- void clusterInit(void) {
- int saveconf = 0;
-
-
- server.cluster = zmalloc(sizeof(clusterState));
-
- server.cluster->myself = NULL;
-
- server.cluster->currentEpoch = 0;
-
- server.cluster->state = REDIS_CLUSTER_FAIL;
-
- server.cluster->size = 1;
- server.cluster->todo_before_sleep = 0;
-
- server.cluster->nodes = dictCreate(&clusterNodesDictType,NULL);
-
- server.cluster->nodes_black_list =
- dictCreate(&clusterNodesBlackListDictType,NULL);
-
- server.cluster->failover_auth_time = 0;
- server.cluster->failover_auth_count = 0;
- server.cluster->failover_auth_rank = 0;
- server.cluster->failover_auth_epoch = 0;
- server.cluster->lastVoteEpoch = 0;
- server.cluster->stats_bus_messages_sent = 0;
- server.cluster->stats_bus_messages_received = 0;
-
- memset(server.cluster->slots,0, sizeof(server.cluster->slots));
-
- clusterCloseAllSlots();
-
-
- if (clusterLockConfig(server.cluster_configfile) == REDIS_ERR)
- exit(1);
-
-
- if (clusterLoadConfig(server.cluster_configfile) == REDIS_ERR) {
-
- myself = server.cluster->myself =
- createClusterNode(NULL,REDIS_NODE_MYSELF|REDIS_NODE_MASTER);
- redisLog(REDIS_NOTICE,"No cluster configuration found, I'm %.40s",
- myself->name);
- clusterAddNode(myself);
- saveconf = 1;
- }
-
-
- if (saveconf) clusterSaveConfigOrDie(1);
-
-
- server.cfd_count = 0;
-
- if (server.port > (65535-REDIS_CLUSTER_PORT_INCR)) {
- redisLog(REDIS_WARNING, "Redis port number too high. "
- "Cluster communication port is 10,000 port "
- "numbers higher than your Redis port. "
- "Your Redis port number must be "
- "lower than 55535.");
- exit(1);
- }
-
- if (listenToPort(server.port+REDIS_CLUSTER_PORT_INCR,
- server.cfd,&server.cfd_count) == REDIS_ERR)
- {
- exit(1);
- } else {
- int j;
-
- for (j = 0; j < server.cfd_count; j++) {
-
- if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,
- clusterAcceptHandler,NULL) == AE_ERR)
- redisPanic("Unrecoverable error creating Redis Cluster "
- "file event.");
- }
- }
-
-
- server.cluster->slots_to_keys = zslCreate();
- resetManualFailover();
- }
好了,至此,和集群相关的初始化就结束了,以后我再写一些添加删除节点以及故障恢复相关的文章,欢迎大家提问哦~~