redis源码学习(集群)

集群是一种分布式的思想,把数据存储到各个节点上去提供服务。分布式一个重要的步骤,就是分片。那redis集群是怎么分片的呢,以及集群服务的稳定性和可靠性怎么保证。下面就来剖析下。

1. 集群是怎么创建的,分片怎么设计的?

分片原理:

redis的核心数据是个hash table,分片是按照key值来划分,再reds里面叫槽(slot),集群中的各个节点都会分一些槽,用于存储它们的数据。

客户端在请求集群的服务时候,通过查询的key,算出一个hash值,再使用hash值%槽数得到第几个槽,再通过对应槽找对应节点,进行查询或者更新操作。

基本数据结构:

集群节点:

// 节点状态
struct clusterNode {

    // 创建节点的时间
    mstime_t ctime; /* Node object creation time. */

    // 节点的名字,由 40 个十六进制字符组成
    // 例如 68eef66df23420a5862208ef5b1a7005b806f2ff
    char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */

    // 节点标识
    // 使用各种不同的标识值记录节点的角色(比如主节点或者从节点),
    // 以及节点目前所处的状态(比如在线或者下线)。
    int flags;      /* REDIS_NODE_... */

    // 节点当前的配置纪元,用于实现故障转移
    uint64_t configEpoch; /* Last configEpoch observed for this node */

    // 由这个节点负责处理的槽
    // 一共有 REDIS_CLUSTER_SLOTS / 8 个字节长
    // 每个字节的每个位记录了一个槽的保存状态
    // 位的值为 1 表示槽正由本节点处理,值为 0 则表示槽并非本节点处理
    // 比如 slots[0] 的第一个位保存了槽 0 的保存情况
    // slots[0] 的第二个位保存了槽 1 的保存情况,以此类推
    unsigned char slots[REDIS_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 */

    // 最后一次发送 PING 命令的时间
    mstime_t ping_sent;      /* Unix time we sent latest ping */

    // 最后一次接收 PONG 回复的时间戳
    mstime_t pong_received;  /* Unix time we received the pong */

    // 最后一次被设置为 FAIL 状态的时间
    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 */

    // 这个节点的复制偏移量
    long long repl_offset;      /* Last known repl offset for this node. */

    // 节点的 IP 地址
    char ip[REDIS_IP_STR_LEN];  /* Latest known IP address of this node */

    // 节点的端口号
    int port;                   /* Latest known port of this node */

    // 保存连接节点所需的有关信息
    clusterLink *link;          /* TCP/IP link with this node */

    // 一个链表,记录了所有其他节点对该节点的下线报告
    list *fail_reports;         /* List of nodes signaling this as failing */

};

这个是集群的原子结构

每个集群节点都有一个对应集群状态的记录,并且server端属性纪录的是集群的状态。

// 集群状态,每个节点都保存着一个这样的状态,记录了它们眼中的集群的样子。
// 另外,虽然这个结构主要用于记录集群的属性,但是为了节约资源,
// 有些与节点有关的属性,比如 slots_to_keys 、 failover_auth_count
// 也被放到了这个结构里面。
typedef struct clusterState {

    // 指向当前节点的指针
    clusterNode *myself;  /* This node */

    // 集群当前的配置纪元,用于实现故障转移
    uint64_t currentEpoch;

    // 集群当前的状态:是在线还是下线
    int state;            /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */

    // 集群中至少处理着一个槽的节点的数量。
    int size;             /* Num of master nodes with at least one slot */

    // 集群节点名单(包括 myself 节点)
    // 字典的键为节点的名字,字典的值为 clusterNode 结构
    dict *nodes;          /* Hash table of name -> clusterNode structures */

    // 节点黑名单,用于 CLUSTER FORGET 命令
    // 防止被 FORGET 的命令重新被添加到集群里面
    // (不过现在似乎没有在使用的样子,已废弃?还是尚未实现?)
    dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */

    // 记录要从当前节点迁移到目标节点的槽,以及迁移的目标节点
    // migrating_slots_to[i] = NULL 表示槽 i 未被迁移
    // migrating_slots_to[i] = clusterNode_A 表示槽 i 要从本节点迁移至节点 A
    clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];

    // 记录要从源节点迁移到本节点的槽,以及进行迁移的源节点
    // importing_slots_from[i] = NULL 表示槽 i 未进行导入
    // importing_slots_from[i] = clusterNode_A 表示正从节点 A 中导入槽 i
    clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];

    // 负责处理各个槽的节点
    // 例如 slots[i] = clusterNode_A 表示槽 i 由节点 A 处理
    clusterNode *slots[REDIS_CLUSTER_SLOTS];

} clusterState;

创建过程:

redis.c--->int main--->initServer--->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();
    //...
}

2. 集群是如何工作的,但一个命令过来,集群是如何执行命令的?

数据库中的所有的槽,比如说是16384个,都进行了指派,集群就会进入上线状态,这时客户端可以像集群中的节点发送数据命令了。

详细的工作原理:

1)一个命令过来,首先得到此命令要查的key,通过key计算它属于哪个槽。

2)先看看当前节点有没有这个槽,如果有直接查询或操作此节点,并返回消息给客户端。

3)如果此节点没有,那么节点会向客户端返回一个MOVED错误,指引客户端转向至正确的节点,并且再次发送命令,并返回结果。

3. 故障转移

集群中的节点可以设置为主从模式,这样实现了重要数据的备份,单一个节点发生故障的时候,可以把此节点下线,使用它的备份节点(从节点)继续工作,不影响集群的操作。

4. 为什么要用集群

集群的好处有两点,

1)能存储更大的数据量,redis是内存数据库,内存的资源有限,使用集群可以达到大数据量的存储。

2)效率更高,当然这个是对于一定规模的数据来说。数据量小的话,也要分库,中间调度的损耗也挺大的。分库集群,相当于多点处理,能够并发进行,效率当然高多了。

5. 为啥mysql没法实现集群,只能使用分表,这种复杂的处理逻辑?

这个更数据存储结构有关系,redis是hash table结构,而mysql底层结构是B+树,hash表好拆分,B+树不好拆。

mysql分表逻辑就比较复杂了,有垂直拆分和水平拆分,垂直简单点,水平复杂些,但是所有的拆分,都涉及到调用的修改。如果前端直接调用的话,那改动量就大了。

时间: 2024-10-04 00:04:36

redis源码学习(集群)的相关文章

Dubbo 源码分析 - 集群容错之 LoadBalance

1.简介 LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载"均摊"到不同的机器上.避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况.通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载.在为高负载的服务器分流的同时,还可以避免资源浪费,一举两得.负载均衡可分为软件负载均衡和硬件负载均衡.在我们日常开发中,一般很难接触到硬件负载均衡.但软件负载均衡还是能够接触到一些的,比如 Nginx.在 Dubbo 中,也有负载均衡的概念和相应的实现

Redis源码学习-Lua脚本

Redis源码学习-Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开发环境搭建>. 要注意的是:在Cygwin中安装Lua解析器后,SublimeClang插件就能识别出可饮用的Lua头文件了,因为Build System中我们已经配置过"-I", "D:\\cygwin64\\usr\\include",而新安装的Lua头文件会

Redis源码学习:字符串

Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串,还用来当做缓冲区,例如AOF缓冲区或输入缓冲区等.如下所示,整数len和free分别表示buf数组中已使用的长度和剩余可用的长度,buf是一个原生C字符串,以\0结尾. sds就是sdshdr中char buf[]的别名,后面能看到,各种操作函数的入参和返回值都是sds而非sdshdr.那sdshd

Redis源码学习-AOF

前言 网络上也有许多介绍redis的AOF机制的文章,但是从宏观上介绍aof的流程,没有具体分析在AOF过程中涉及到的数据结构和控制机制.昨晚特别看了2.8源码,感觉源码中的许多细节是值得细细深究的.特别是list *aof_rewrite_buf_blocks结构.仔细看源码,会发现原来看网络文章多的到的领会是片面的,最好的学习还是得自己动手... 原文链接: http://blog.csdn.net/ordeder/article/details/39271543 作者提及的AOF简化的流程

Dubbo 源码分析 - 集群容错之 Directory

1. 简介 前面文章分析了服务的导出与引用过程,从本篇文章开始,我将开始分析 Dubbo 集群容错方面的源码.这部分源码包含四个部分,分别是服务目录 Directory.服务路由 Router.集群 Cluster 和负载均衡 LoadBalance.这几个部分的源码逻辑比较独立,我会分四篇文章进行分析.本篇文章作为集群容错的开篇文章,将和大家一起分析服务目录相关的源码.在进行深入分析之前,我们先来了解一下服务目录是什么.服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取

Dubbo 源码分析 - 集群容错之 Router

1. 简介 上一篇文章分析了集群容错的第一部分 – 服务目录 Directory.服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由.上一篇文章关于服务路由相关逻辑没有细致分析,一笔带过了,本篇文章将对此进行详细的分析.首先,先来介绍一下服务目录是什么.服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者.Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter.脚本路由 ScriptRo

redis源码学习(客户端)

大概介绍 redis 客户端设计主要是存储客户的链接,请求,请求解析的命令,执行结果.先看server的结构和client的结构,server里面有多个client,相当于一个服务端可以连多个客户端,服务端根据事件触发模式依次处理客户端的请求. server结构 struct redisServer { /* General */ // 配置文件的绝对路径 char *configfile; /* Absolute config file path, or NULL */ // serverCr

redis源码学习_整数集合

redis里面的整数集合保存的都是整数,有int_16.int_32和int_64这3种类型,和C++中的set容器差不多. 同时具备如下特点: 1.set里面的数不重复,均为唯一. 2.set里面的数是从小到大有序的,这在后面的intsetAdd函数中可以看到. 然后由于我们可以同时存储int_16.int_32和int_64这3种类型,一开始只能为一种类型.假设为int_32,那么我们要插入一个int_16类型的数,只需要找到位置直接插入就可以了:但是我们要插入一个int_64类型的数,我们

redis源码学习_链表

redis的链表是双向链表,该链表不带头结点,具体如下: 主要总结一下adlist.c和adlist.h里面的关键结构体和函数. 链表节点结构如下: 1 /* 2 * 双端链表节点 3 */ 4 typedef struct listNode { 5 6 // 前置节点 7 struct listNode *prev; //如果是list的头结点,则prev指向NULL 8 9 // 后置节点 10 struct listNode *next;//如果是list尾部结点,则next指向NULL