redis集群实现(一)集群架构与初始化

Redis是一个高可用、高性能、高可扩展性的基于内存也支持持久化存储的kv存储数据库redis相比较于之前的kv存储memcached而言,不但支持的value类型大大增加,并且还支持数据的持久化,弥补了memcached的不能持久化的缺点,但是在3.0之前的redis并不支持集群功能,这也是redis3.0之前不能被大量部署的一个原因,但是由于3.0以后的redis支持了集群功能,redis就开始大量的替代之前的memcached,今天我从源代码层次学习下redis是怎么实现集群功能的。

我看的源代码是redis-3.0源代码,可以在下边这个链接下载到。

http://download.redis.io/releases/redis-3.0.0-rc1.tar.gz

redis的集群并不是类似于HDFS之类的namenodedatanode之类的架构,而是采用改进的一致性哈希算法来对数据进行分片,平均的分配到每一个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集群。

首先看看和集群有关的数据结构,这些都是集群实现的基础。

[cpp]  view plain  copy
  1. // 节点状态结构体  
  2. struct clusterNode {  
  3.   
  4.     // 创建节点的时间  
  5.     mstime_t ctime;   
  6.   
  7.     // 节点ID,通过随机数生成,长度为40,每一个字符都是一个16进制字符  
  8.     char name[REDIS_CLUSTER_NAMELEN];  
  9.   
  10.     // 节点状态标识位,比如标识节点是主节点还是从节点。  
  11.     int flags;    
  12.   
  13.     // 节点当前的配置纪元  
  14.     uint64_t configEpoch;   
  15.   
  16.     // 这个node存储的数据槽位图,REDIS_CLUSTER_SLOTS就是redis集群分成的块数目,相当于上边的16384,如果值是1代表这个数据槽的数据存储在当前节点,如果是0表示不在这个节点。  
  17.     unsigned char slots[REDIS_CLUSTER_SLOTS/8];   
  18.   
  19.     // 这个node存储的数据槽的数目  
  20.     int numslots;    
  21.   
  22.     // 如果本节点是主节点,这个字段表示从节点的数目  
  23.     int numslaves;   
  24.   
  25.     // 指针数组,指向各个从节点  
  26.     struct clusterNode **slaves;  
  27.   
  28.     // 如果这是一个从节点,那么指向主节点  
  29.     struct clusterNode *slaveof;                                                                                              
  30.   
  31.     // 最后一次发送 PING数据包的时间  
  32.     mstime_t ping_sent;        
  33.   
  34.     // 最后一次接收 PONG数据包的时间戳  
  35.     mstime_t pong_received;   
  36.   
  37.     // 最后一次被设置为 FAIL状态的时间  
  38.     mstime_t fail_time;       
  39.   
  40.     // 最后一次给某个从节点投票的时间  
  41.     mstime_t voted_time;      
  42.   
  43.     // 最后一次从这个节点接收到复制偏移量的时间  
  44.     mstime_t repl_offset_time;   
  45.   
  46.     // 这个节点的复制偏移量  
  47.     long long repl_offset;       
  48.   
  49.     // 节点的 IP地址  
  50.     char ip[REDIS_IP_STR_LEN];    
  51.   
  52.     // 节点的端口号  
  53.     int port;                 
  54.   
  55.     // 保存连接相关的信息  
  56.     clusterLink *link;   
  57.   
  58.     // 一个链表,记录了所有其他节点对该节点的下线报告  
  59.     list *fail_reports;   
  60. };  

然后是记录集群之间的连接的结构体

[cpp]  view plain  copy
  1. // clusterLink 包含了与其他节点进行通讯所需的全部信息  
  2. typedef struct clusterLink {                                                                                                                                    
  3.   
  4.     // 连接的创建时间  
  5.     mstime_t ctime;          
  6.   
  7.     // TCP 套接字描述符  
  8.     int fd;            
  9.   
  10.     // 输出缓冲区,保存着等待发送给其他节点的消息(message)。  
  11.     sds sndbuf;       
  12.   
  13.     // 输入缓冲区,保存着从其他节点接收到的消息。  
  14.     sds rcvbuf;       
  15.   
  16.     // 与这个连接相关联的节点,如果没有的话就为 NULL  
  17.     struct clusterNode *node;  
  18.   
  19. } clusterLink;  

然后是记录集群状态的结构体,每一个节点都有一个这个结构体,用来表示当前集群的状态。

[cpp]  view plain  copy
  1. typedef struct clusterState {  
  2.                                                                                                                                                                 
  3.     // 指向当前节点的指针  
  4.     clusterNode *myself;  
  5.   
  6.     // 集群当前的配置纪元,用于实现故障转移  
  7.     uint64_t currentEpoch;  
  8.   
  9.     // 集群当前的状态:是在线还是下线  
  10.     int state;  
  11.   
  12.     // 集群中至少处理着一个槽的节点的数量。  
  13.     int size;     
  14.   
  15.     // 集群节点名单(包括 myself节点)  
  16.     // 字典的键为节点的名字,字典的值为 clusterNode结构  
  17.     dict *nodes;     
  18.   
  19.     // 节点黑名单,用于 CLUSTER FORGET命令  
  20.     // 防止被 FORGET的命令重新被添加到集群里面  
  21.     // (不过现在似乎没有在使用的样子,已废弃?还是尚未实现?)  
  22.     dict *nodes_black_list;  
  23.   
  24.     // 记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点  
  25.     // migrating_slots_to[i] = NULL 表示槽 i未被迁移  
  26.     // migrating_slots_to[i] = clusterNode_A 表示槽 i要从本节点迁移至节点 A  
  27.     clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];  
  28.   
  29.     // 记录要从源节点迁移到本节点的槽,以及进行迁移的源节点  
  30.     // importing_slots_from[i] = NULL 表示槽 i未进行导入  
  31.     // importing_slots_from[i] = clusterNode_A 表示正从节点 A中导入槽 i  
  32.     clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];  
  33.   
  34.     // 负责处理各个槽的节点  
  35.     // 例如 slots[i] = clusterNode_A表示槽 i 由节点 A处理  
  36.     clusterNode *slots[REDIS_CLUSTER_SLOTS];  
  37.   
  38.     // 跳跃表,表中以槽作为分值,键作为成员,对槽进行有序排序  
  39.     // 当需要对某些槽进行区间(range)操作时,这个跳跃表可以提供方便  
  40.     // 具体操作定义在 db.c里面  
  41.     zskiplist *slots_to_keys;  
  42.   
  43.     // 以下这些域被用于进行故障转移选举  
  44.   
  45.     // 上次执行选举或者下次执行选举的时间  
  46.     mstime_t failover_auth_time;  
  47.   
  48.     // 节点获得的投票数量  
  49.     int failover_auth_count;   
  50.   
  51.     // 如果值为 1,表示本节点已经向其他节点发送了投票请求  
  52.     int failover_auth_sent;   
  53.   
  54.     int failover_auth_rank;  
  55.   
  56.     uint64_t failover_auth_epoch;   
  57.   
  58.     /* 共用的手动故障转移状态 */  
  59.   
  60.     // 手动故障转移执行的时间限制  
  61.     mstime_t mf_end;             
  62.   
  63.     /* 主服务器的手动故障转移状态 */  
  64.     clusterNode *mf_slave;       
  65.   
  66.     /* 从服务器的手动故障转移状态 */  
  67.     long long mf_master_offset;    // 指示手动故障转移是否可以开始的标志值  
  68.     // 值为非 0时表示各个主服务器可以开始投票  
  69.     int mf_can_start;            
  70.   
  71.     /* The followign fields are uesd by masters to take state on elections. */  
  72.     /* 以下这些域由主服务器使用,用于记录选举时的状态 */  
  73.   
  74.     // 集群最后一次进行投票的纪元  
  75.     uint64_t lastVoteEpoch;     
  76.   
  77.     // 在进入下个事件循环之前要做的事情,以各个 flag来记录  
  78.     int todo_before_sleep;  
  79.   
  80.     // 通过 cluster连接发送的消息数量  
  81.     long long stats_bus_messages_sent;   
  82.   
  83.     // 通过 cluster接收到的消息数量  
  84.     long long stats_bus_messages_received;   
  85. } 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_enabled1,表示集群功能的开启。

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函数,看看单机集群设置的初始化代码

[cpp]  view plain  copy
  1. // 初始化集群  
  2. void clusterInit(void) {  
  3.     int saveconf = 0;   
  4.   
  5.     // 初始化配置,server.cluster就是clusterState结构体,每一个节点保存一个。  
  6.     server.cluster = zmalloc(sizeof(clusterState));  
  7.     //指向自己的节点指针  
  8.     server.cluster->myself = NULL;  
  9.     //初始配置纪元为0  
  10.     server.cluster->currentEpoch = 0;   
  11.     //初始配置状态fail  
  12.     server.cluster->state = REDIS_CLUSTER_FAIL;  
  13.     //集群数目为1  
  14.     server.cluster->size = 1;   
  15.     server.cluster->todo_before_sleep = 0;   
  16.     //建立节点映射的哈希结构体  
  17.     server.cluster->nodes = dictCreate(&clusterNodesDictType,NULL);  
  18.     //节点的黑名单。。  
  19.     server.cluster->nodes_black_list =  
  20.         dictCreate(&clusterNodesBlackListDictType,NULL);  
  21.     //执行选举相关的变量初始化  
  22.     server.cluster->failover_auth_time = 0;   
  23.     server.cluster->failover_auth_count = 0;   
  24.     server.cluster->failover_auth_rank = 0;   
  25.     server.cluster->failover_auth_epoch = 0;   
  26.     server.cluster->lastVoteEpoch = 0;   
  27.     server.cluster->stats_bus_messages_sent = 0;   
  28.     server.cluster->stats_bus_messages_received = 0;   
  29.     //初始化槽  
  30.     memset(server.cluster->slots,0, sizeof(server.cluster->slots));  
  31.     //把槽数组清零  
  32.     clusterCloseAllSlots();  
  33.   
  34.     /* 锁住集群配置文件,确保每个每个节点使用的是自己的配置文件 */  
  35.     if (clusterLockConfig(server.cluster_configfile) == REDIS_ERR)  
  36.         exit(1);  
  37.   
  38.     /* 载入本节点的集群配置文件. */  
  39.     if (clusterLoadConfig(server.cluster_configfile) == REDIS_ERR) {  
  40.         /* 如果没有发现集群配置文件,就把自己加入到集群里. */  
  41.         myself = server.cluster->myself =  
  42.             createClusterNode(NULL,REDIS_NODE_MYSELF|REDIS_NODE_MASTER);  
  43.         redisLog(REDIS_NOTICE,"No cluster configuration found, I'm %.40s",  
  44.             myself->name);  
  45.         clusterAddNode(myself);  
  46.         saveconf = 1;  
  47.     }  
  48.   
  49.     // 保存 nodes.conf文件  
  50.     if (saveconf) clusterSaveConfigOrDie(1);  
  51.   
  52.     // 监听 TCP端口  
  53.     server.cfd_count = 0;  
  54.   
  55.     if (server.port > (65535-REDIS_CLUSTER_PORT_INCR)) {  
  56.         redisLog(REDIS_WARNING, "Redis port number too high. "  
  57.                    "Cluster communication port is 10,000 port "  
  58.                    "numbers higher than your Redis port. "  
  59.                    "Your Redis port number must be "  
  60.                    "lower than 55535.");  
  61.         exit(1);  
  62.     }  
  63.     //监听本节点的端口号  
  64.     if (listenToPort(server.port+REDIS_CLUSTER_PORT_INCR,  
  65.         server.cfd,&server.cfd_count) == REDIS_ERR)  
  66.     {  
  67.         exit(1);  
  68.     } else {  
  69.         int j;  
  70.   
  71.         for (j = 0; j < server.cfd_count; j++) {  
  72.             // 关联监听事件处理器  
  73.             if (aeCreateFileEvent(server.el, server.cfd[j], AE_READABLE,  
  74.                 clusterAcceptHandler,NULL) == AE_ERR)                                                                                                          
  75.                     redisPanic("Unrecoverable error creating Redis Cluster "  
  76.                                 "file event.");  
  77.         }  
  78.     }  
  79.   
  80.     // slots -> keys 映射是一个有序集合,基础实现是跳跃链表,分值是槽号,返回的是key值  
  81.     server.cluster->slots_to_keys = zslCreate();  
  82.     resetManualFailover();  
  83. }  
好了,至此,和集群相关的初始化就结束了,以后我再写一些添加删除节点以及故障恢复相关的文章,欢迎大家提问哦~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值