Redis设计与实现集群工作原理
上一篇:Redis设计与实现-哨兵模式原理
主从复制、哨兵模式逐步提高了集群的可用性,但是都还达不到大企业在高并发业务场景下的使用要求,于是Redis在主从复制的基础上架构了一层,搞了一个集群出来
概要
Sentinel模式相比主从模式提供了更高的可用性,但是依然不够完美,因为主节点发生故障与进行故障转移的时候,服务器是阻塞的,不能提供服务,对于高并发、超低延迟的业务是不可忍受的,甚至可能导致整个系统崩掉。
Redis集群通过将数据进行分片,同时提供复制和故障转移功能更高的保证了系统的可用新,如果主节点够多,那么集群的可用性越高(槽指派越分散)
下面对集群的节点及其数据结构、槽指派、命令执行过程、重新分片、故障转移进行介绍
另:通过在配置文件中开启cluster-enabled配置,即可配置集群。。。本篇重点不是如何配置集群,而是介绍集群的工作原理,如果要看集群的配置请参考Redis集群搭建与使用方法
架构图
集群由多个节点组成,每个节点必须是主从模式是中的主节点,它负责处理槽位,每个节点的数据结构为clusterNode,集群中每一个节点只能使用0号数据库(每个redisServer中都有16个db,默认是第0号db)。
数据结构
typedef struct clusterNode{//节点创建时间
mstime_t ctime;
//节点名称,生成和RUN ID一样 40位16进制随机字符串
char name[REDIS_CLUSTER_NAMELEN]
//标记节点状态或者主、从节点等
int flags;
//当前配置纪元,每进行一次选举都会+1
uint64_t configEpoch;
//节点的IP地址
char ip[REDIS_IP_STR_LEN];
//节点的端口
int port;
//连接点所需的相关信息
clusterLink * link;
//集群状态
clusterState state;
//被分配槽的索引位置
unsigned char slots[16384/8];
//被分配槽的个数
int numslots;
}
typedef struct clusterLink{//clusterLink创建时间
mstime_t ctime;
//套接字描述符
int fd;
//发送缓冲区
sds sndbuf;
//接收缓冲区
sds rcvbuf;
//与连接绑定节点的指针,没有为NULL
clusterNode * node
}
typedef struct clusterState{//指向当前节点的指针
clusterNode *myself;
//集群当前的配置纪元
uint64_t currentEpoch;
//集群状态:在线或者下线
int state;
//集群中至少处理着一个槽的数量
int size;
//集群中所有节点
//KEY:节点名字
//VALUE:clusterNode
dict *nodes;
//记录16384个槽对应的节点,通过它只需要O(1)的时间代价即可知道某一个槽位所在节点
clusterNode *slots[16384];
//当前节点正在从其他节点导入的槽--重新分片相关
clusterNode * import_slots_from[16384];
//当前节点正在向其他节点迁移的槽--重新分片相关
clusterNode * migrating_slots_to[16384];
}
请认证仔细的看一遍数据结构,对于后面的理解非常重要
槽指派
Redis集群将未来所有需要处理的数据划分为2^14=16384份,根据服务器的多少,由安装与运维人员将这16384份分配给不同的机器,因此当写命令执行的时候会先根据KEY计算出槽位,然后通过clusterNode.state.slots定位出槽位是那个主节点在负责,然后客户端连接到该主节点进行执行。
而槽指派就是把人为将16384份分配到不同机器的过程记录到数据结构中,让集群中的所有节点都知道划分的结果。
每个节点通过clusterNode.slots和clusterNode.numslots记录该节点被分配的槽位置和槽的个数
每个节点通过clusterNode.state.slots记录每个槽位是由谁(主节点)负责
举例:
假设集群中有10001、10002、10003三个节点,它们分别分配了如下槽位:10001:0~5000
10002:5001~10000
10003:10001~16383
另:因为clusterNode.slots是一个char的数组(二进制数组),因此可以用O(1)的时间代价去定位该节点在某个槽位的值是否为1,如果等于1则表示该槽位在集群中由该节点负责,否则是其他节点。
通过CLUSTER MEET 和CLUSTER ADDSLOTS命令之后某个节点的槽位相关信息被设置好,然后还会将该信息发送给其他节点,其他节点会将clusterNode.clusterState.nodes中对应节点的clusterNode结构更新,这样集群中的每个节点都知道了每个槽位被指派给了那个节点。
但Redis并不是遍历所有clusterNode的slots判断槽位所在节点,这样效率太低,而是通过前面提到的clusterNode.state.slots快速定位
命令执行过程
当客户端与任意一个节点连接之后,按照如下步骤完成命令执行
节点通过crc16(KEY)&16384得到槽位
节点根据clusteNode.clusterState.slots快速拿到clusterNode
比较上一步的clusterNode是不是自己,如果是则执行命令
如果不是,则返回一个MOVED信息,客户端从MOVED信息中拿到槽位对应的节点信息,连接并执行命令:这个过程叫redirect。。。有的时候连接是已经早就创建好了的,只有当连接不存在的时候才会新建连接
重新分片(扩容与维护)
随着业务的发展也许现有服务器不能够很好的支撑,需要新加几台服务器,那么就需要重新指派操作,也就是所谓的重新分片。
Redis提供的数据结构保证了在重新分片的同时,集群是可持续提供服务的。
步骤
目标节点准备导入槽slot的键值对-也就是为键分配内存空间等
源节点做好迁移槽slot对应键值对的准备工作
将源节点中的键原子的迁移到目标节点
将槽slot指派给目标节点
如果在分片期间执行命令的KEY计算出来的槽位正好是正在迁移的槽,则按照如下描述进行:
是先在源节点中找,如果有则在源节点中执行命令,否则看该槽是否正在被迁移,如果没有则到源节点中执行命令,否则返回一个ASK错误,客户端收到回复后目标节点中去执行命令。
这一系列过程中涉及到一个ASK自动redirect的操作,和MOVED非常相似。
故障转移
故障检测
每秒进行一次ping命令,如果在规定的时间内没有返回PONG消息,那么ping节点将被ping的节点标记为"疑似下线"
如果有过半的主节点将某一个节点标记为疑似下线,则将该节点标记为下线
转移故障
步骤如下:
从下线主节点的所有从节点里面选举一个作为主节点:该节点要执行slaveof no one
新主节点撤销所有已下线主节点的槽指派,并将这些槽全部指派给自己
新的主节点向所有节点广播一条PONG消息,其他主节点立刻就会知道新的主节点,并且接管了已下线主节点的所有槽指派
新的主节点开始接受并执行命令
已下线主节点变成从节点,带它上线后重新从新的主节点复制数据
选举主节点
整个选举过程与哨兵模式的选择领头Sentinel非常类似,大体过程是这样的:
从服务器发现主服务器下线,则会向集群中的所有主节点(有槽指派的节点)广播一条消息,让它们将自己设置为主节点,而这些节点在一个配置纪元里面只有一次设置机会,因此可以保证一个配置纪元只有一个过半的节点被选举出来
总结
Redis Cluster集群提供了非常好高可用性、分片技术支持热扩展(不停机)。因为槽的设计,如果某一个主节点挂掉,在新的主节点选举出来之前只会有固定槽位的键不可用,如果主节点足够多,那么影响就越来越小,但是服务器成本也高了。因此它只适合大企业,不差钱的。
以上是 Redis设计与实现集群工作原理 的全部内容, 来源链接: utcz.com/z/512638.html