共计 8424 个字符,预计需要花费 22 分钟才能阅读完成。
码哥出品,必属精品。关注公众号「码哥字节」并加码哥微信(MageByte1024),窥探硬核文章背地的男人的另一面。
本文将对集群的节点、槽指派、命令执行、从新分片、转向、故障转移、音讯等各个方面进行深刻拆解。
目标在于把握什么是 Cluster?Cluster 分片原理,客户端定位数据原理、故障切换,选主,什么场景应用 Cluster,如何部署集群 ……
[toc]
为什么须要 Cluster
65 哥:码哥,自从用上了你说的哨兵集群实现故障主动转移后,我终于能够开心的跟女朋友么么哒也不怕 Redis 宕机深夜宕机了。
可是最近遇到一个糟心的问题,Redis 须要保留 800 万个键值对,占用 20 GB 的内存。
我就应用了一台 32G 的内存主机部署,然而 Redis 响应有时候十分慢,应用 INFO 命令查看 latest_fork_usec 指标(最近一次 fork 耗时),发现特地高。
次要是 Redis RDB 长久化机制导致的,Redis 会 Fork 子过程实现 RDB 长久化操作,fork 执行的耗时与 Redis 数据量成正相干。
而 Fork 执行的时候会阻塞主线程,因为数据量过大导致阻塞主线程过长,所以呈现了 Redis 响应慢的表象。
65 哥:随着业务规模的拓展,数据量越来越大。主从架构降级单个实例硬件难以拓展,且保留大数据量会导致响应慢问题,有什么方法能够解决么?
保留大量数据,除了应用大内存主机的形式,咱们还能够应用切片集群。俗话说「众人拾材火焰高」,一台机器无奈保留所有数据,那就多台分担。
应用 Redis Cluster 集群,次要解决了大数据量存储导致的各种慢问题,同时也便于横向拓展。
两种计划对应着 Redis 数据增多的两种拓展计划:垂直扩大(scale up)、程度扩大(scale out)。
- 垂直拓展:降级单个 Redis 的硬件配置,比方减少内存容量、磁盘容量、应用更弱小的 CPU。
- 程度拓展:横向减少 Redis 实例个数,每个节点负责一部分数据。
比方须要一个内存 24 GB 磁盘 150 GB 的服务器资源,有以下两种计划:
在面向百万、千万级别的用户规模时,横向扩大的 Redis 切片集群会是一个十分好的抉择。
65 哥:那这两种计划都有什么优缺点呢?
- 垂直拓展部署简略,然而当数据量大并且应用 RDB 实现长久化,会造成阻塞导致响应慢。另外受限于硬件和老本,拓展内存的老本太大,比方拓展到 1T 内存。
- 程度拓展便于拓展,同时不须要放心单个实例的硬件和老本的限度。然而,切片集群会波及多个实例的分布式治理问题,须要解决如何将数据正当散布到不同实例,同时还要让客户端能正确拜访到实例上的数据。
什么是 Cluster 集群
Redis 集群是一种分布式数据库计划,集群通过分片(sharding)来进行数据管理(「分治思维」的一种实际),并提供复制和故障转移性能。
将数据划分为 16384 的 slots,每个节点负责一部分槽位。槽位的信息存储于每个节点中。
它是去中心化的,如图所示,该集群有三个 Redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。
三个节点相互连接组成一个对等的集群,它们之间通过 Gossip
协定互相交互集群信息,最初每个节点都保留着其余节点的 slots 分配情况。
开篇寄语
技术不是万能的,程序员也不是最厉害的,肯定要搞清楚,不要感觉「老子天下第一」。一旦有了这个意识,可能会耽搁咱们的成长。
技术是为了解决问题的,如果说一个技术不能解决问题,那这个技术就一文不值。
不要去炫技,没有意义。
集群装置
点击 ->《Redis 6.X Cluster 集群搭建》查看
一个 Redis 集群通常由多个节点(node)组成,在刚开始的时候,每个节点都是互相独立的,它们都处于一个只蕴含本人的集群当中,要组建一个真正可工作的集群,咱们必须将各个独立的节点连接起来,形成一个蕴含多个节点的集群。
连贯各个节点的工作能够通过 CLUSTER MEET
命令实现:CLUSTER MEET <ip> <port>
。
向一个节点 node 发送 CLUSTER MEET
命令,能够让 node 节点与 ip 和 port 所指定的节点进行握手(handshake),当握手胜利时,node 节点就会将 ip 和 port 所指定的节点增加到 node 节点以后所在的集群中。
就如同 node 节点说:“喂,ip = xx,port = xx 的老哥,要不要退出「码哥字节」技术群,退出集群就找到了一条大神成长之路,关注「码哥字节」公众号回复「加群」,是兄弟就跟我一起来!”
对于 Redis Cluster 集群搭建具体步骤,请点击文末左下角「浏览原文」或者点击 ->《Redis 6.X Cluster 集群搭建》查看,官网对于 Redis Cluster 的详情请看:https://redis.io/topics/clust…
Cluster 实现原理
65 哥:数据切片后,须要将数据分布在不同实例上,数据和实例之间如何对应上呢?
Redis 3.0 开始,官网提供了 Redis Cluster 计划实现了切片集群,该计划就实现了数据和实例的规定。Redis Cluster 计划采纳哈希槽(Hash Slot,接下来我会间接称之为 Slot),来解决数据和实例之间的映射关系。
跟着「码哥字节」一起进入 Cluster 实现原理探索之旅……
将数据分成多份存在不同实例上
集群的整个数据库被分为 16384 个槽(slot),数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点能够解决 0 个或最多 16384 个槽。
Key 与哈希槽映射过程能够分为两大步骤:
- 依据键值对的 key,应用 CRC16 算法,计算出一个 16 bit 的值;
- 将 16 bit 的值对 16384 执行取模,失去 0 ~ 16383 的数示意 key 对应的哈希槽。
Cluster 还容许用户强制某个 key 挂在特定槽位上,通过在 key 字符串外面嵌入 tag 标记,这就能够强制 key 所挂在的槽位等于 tag 所在的槽位。
哈希槽与 Redis 实例映射
65 哥:哈希槽又是如何映射到 Redis 实例上呢?
在 部署集群的样例中通过 cluster create
创立,Redis 会主动将 16384 个 哈希槽均匀散布在集群实例上,比方 N 个节点,每个节点上的哈希槽数 = 16384 / N 个。
除此之外,能够通过 CLUSTER MEET
命令将 7000、7001、7002 三个节点连在一个集群,然而集群目前仍然处于下线状态,因为三个实例都没有解决任何哈希槽。
能够应用 cluster addslots
命令,指定每个实例上的哈希槽个数。
65 哥:为啥要手动制订呢?
能者多劳嘛,退出集群中的 Redis 实例配置不一样,如果承当一样的压力,对于垃圾机器来说就太难了,让牛逼的机器多反对一点。
三个实例的集群,通过上面的指令为每个实例调配哈希槽:实例 1
负责 0 ~ 5460 哈希槽,实例 2
负责 5461~10922 哈希槽,实例 3
负责 10923 ~ 16383 哈希槽。
redis-cli -h 172.16.19.1 –p 6379 cluster addslots 0,5460
redis-cli -h 172.16.19.2 –p 6379 cluster addslots 5461,10922
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 10923,16383
键值对数据、哈希槽、Redis 实例之间的映射关系如下:
Redis 键值对的 key「码哥字节」「牛逼」通过 CRC16 计算后再对哈希槽总个数 16394 取模,模数后果别离映射到实例 1 与实例 2 上。
切记,当 16384 个槽都调配齐全,Redis 集群能力失常工作。
复制与故障转移
65 哥:Redis 集群如何实现高可用呢?Master 与 Slave 还是读写拆散么?
Master 用于解决槽,Slave 节点则通过《Redis 主从架构数据同步》形式同步主节点数据。
当 Master 下线,Slave 代替主节点持续解决申请。主从节点之间并没有读写拆散,Slave 只用作 Master 宕机的高可用备份。
Redis Cluster 能够为每个主节点设置若干个从节点,单主节点故障时,集群会主动将其中某个从节点晋升为主节点。
如果某个主节点没有从节点,那么当它产生故障时,集群将齐全处于不可用状态。
不过 Redis 也提供了一个参数 cluster-require-full-coverage
能够容许局部节点故障,其它节点还能够持续提供对外拜访。
比方 7000 主节点宕机,作为 slave 的 7003 成为 Master 节点持续提供服务。当下线的节点 7000 从新上线,它将成为以后 70003 的从节点。
故障检测
65 哥:在《Redis 高可用篇:Sentinel 哨兵集群原理》我晓得哨兵通过监控、主动切换主库、告诉客户端实现故障主动切换,
Cluster
又如何实现故障主动转移呢?
一个节点认为某个节点失联了并不代表所有的节点都认为它失联了。只有当大多数负责解决 slot 节点都认定了某个节点下线了,集群才认为该节点须要进行主从切换。
Redis 集群节点采纳 Gossip
协定来播送本人的状态以及本人对整个集群认知的扭转。比方一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群播送,其它节点也就能够收到这点失联信息。
对于 Gossip
协定可浏览悟空哥的一篇文章:《病毒入侵,全靠分布式》
如果一个节点收到了某个节点失联的数量 (PFail Count) 曾经达到了集群的大多数,就能够标记该节点为确定下线状态 (Fail),而后向整个集群播送,强制其它节点也接管该节点曾经下线的事实,并立刻对该失联节点进行主从切换。
故障转移
当一个 Slave 发现自己的主节点进入已下线状态后,从节点将开始对下线的主节点进行故障转移。
- 从下线的 Master 及节点的 Slave 节点列表抉择一个节点成为新主节点。
- 新主节点会撤销所有对已下线主节点的 slot 指派,并将这些 slots 指派给本人。
- 新的主节点向集群播送一条 PONG 音讯,这条 PONG 音讯能够让集群中的其余节点立刻晓得这个节点曾经由从节点变成了主节点,并且这个主节点曾经接管了本来由已下线节点负责解决的槽。
- 新的主节点开始接管解决槽无关的命令申请,故障转移实现。
选主流程
65 哥:新的主节点如何选举产生的?
- 集群的配置纪元 +1,是一个自曾计数器,初始值 0,每次执行故障转移都会 +1。
- 检测到主节点下线的从节点向集群播送一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST
音讯,要求所有收到这条音讯、并且具备投票权的主节点向这个从节点投票。 - 这个主节点尚未投票给其余从节点,那么主节点将向要求投票的从节点返回一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
音讯,示意这个主节点反对从节点成为新的主节点。 - 参加选举的从节点都会接管
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
音讯,如果收集到的票 >= (N/2) + 1 反对,那么这个从节点就被选举为新主节点。 - 如果在一个配置纪元外面没有从节点能收集到足够多的反对票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。
跟哨兵相似,两者都是基于 Raft 算法来实现的,流程如图所示:
用表保留键值对和实例的关联关系可行么
65 哥,我来考考你:“Redis Cluster 计划通过哈希槽的形式把键值对调配到不同的实例上,这个过程须要对键值对的 key 做 CRC 计算并对 哈希槽总数取模映射到实例上。如果用一个表间接把键值对和实例的对应关系记录下来(例如键值对 1 在实例 2 上,键值对 2 在实例 1 上),这样就不必计算 key 和哈希槽的对应关系了,只用查表就行了,Redis 为什么不这么做呢?”
应用一个全局表记录的话,如果键值对和实例之间的关系扭转(从新分片、实例增减),须要批改表。如果是单线程操作,所有操作都要串行,性能太慢。
多线程的话,就波及到加锁,另外,如果键值对数据量十分大,保留键值对与实例关系的表数据所须要的存储空间也会很大。
而哈希槽计算,尽管也要记录哈希槽与实例工夫的关系,然而哈希槽的数量少得多,只有 16384 个,开销很小。
客户端如何定位数据所在实例
65 哥:客户端又怎么确定拜访的数据到底散布在哪个实例上呢?
Redis 实例会将本人的哈希槽信息通过 Gossip 协定发送给集群中其余的实例,实现了哈希槽调配信息的扩散。
这样,集群中的每个实例都有所有哈希槽与实例之间的映射关系信息。
在切片数据的时候是将 key 通过 CRC16 计算出一个值再对 16384 取模失去对应的 Slot,这个计算工作能够在客户端上执行发送申请的时候执行。
然而,定位到槽当前还须要进一步定位到该 Slot 所在 Redis 实例。
当客户端连贯任何一个实例,实例就将哈希槽与实例的映射关系响应给客户端,客户端就会将哈希槽与实例映射信息缓存在本地。
当客户端申请时,会计算出键所对应的哈希槽,在通过本地缓存的哈希槽实例映射信息定位到数据所在实例上,再将申请发送给对应的实例。
重新分配哈希槽
65 哥:哈希槽与实例之间的映射关系因为新增实例或者负载平衡重新分配导致扭转了咋办?
集群中的实例通过 Gossip 协定相互传递音讯获取最新的哈希槽调配信息,然而,客户端无奈感知。
Redis Cluster 提供了重定向机制:客户端将申请发送到实例上,这个实例没有相应的数据,该 Redis 实例会通知客户端将申请发送到其余的实例上。
65 哥:Redis 如何告知客户端重定向拜访新实例呢?
分为两种状况:MOVED 谬误、ASK 谬误。
MOVED 谬误
MOVED 谬误(负载平衡,数据曾经迁徙到其余实例上):当客户端将一个键值对操作申请发送给某个实例,而这个键所在的槽并非由本人负责的时候,该实例会返回一个 MOVED 谬误指引转向正在负责该槽的节点。
GET 公众号: 码哥字节
(error) MOVED 16330 172.17.18.2:6379
该响应示意客户端申请的键值对所在的哈希槽 16330 迁徙到了 172.17.18.2 这个实例上,端口是 6379。这样客户端就与 172.17.18.2:6379 建设连贯,并发送 GET 申请。
同时,客户端还会更新本地缓存,将该 slot 与 Redis 实例对应关系更新正确。
ASK 谬误
65 哥:如果某个 slot 的数据比拟多,局部迁徙到新实例,还有一部分没有迁徙咋办?
如果申请的 key 在以后节点找到就间接执行命令,否则时候就须要 ASK 谬误响应了,槽局部迁徙未实现的状况下,如果须要拜访的 key 所在 Slot 正在从从 实例 1 迁徙到 实例 2,实例 1 会返回客户端一条 ASK 报错信息:客户端申请的 key 所在的哈希槽正在迁徙到实例 2 上,你先给实例 2 发送一个 ASKING 命令,接着发发送操作命令。
GET 公众号: 码哥字节
(error) ASK 16330 172.17.18.2:6379
比方客户端申请定位到 key =「公众号: 码哥字节」的槽 16330 在实例 172.17.18.1 上,节点 1 如果找失去就间接执行命令,否则响应 ASK 错误信息,并指引客户端转向正在迁徙的指标节点 172.17.18.2。
留神:ASK 谬误指令并不会更新客户端缓存的哈希槽调配信息。
所以客户端再次申请 Slot 16330 的数据,还是会先给 172.17.18.1
实例发送申请,只不过节点会响应 ASK 命令让客户端给新实例发送一次申请。
MOVED
指令则更新客户端本地缓存,让后续指令都发往新实例。
集群能够设置多大?
65 哥:有了 Redis Cluster,再也不怕大数据量了,我能够有限程度拓展么?
答案是否定的,Redis 官网给的 Redis Cluster 的规模上线是 1000 个实例。
65 哥:到底是什么限度了集群规模呢?
关键在于实例间的通信开销,Cluster 集群中的每个实例都保留所有哈希槽与实例对应关系信息(Slot 映射到节点的表),以及本身的状态信息。
在集群之间每个实例通过 Gossip
协定流传节点的数据,Gossip
协定工作原理大略如下:
- 从集群中随机抉择一些实例依照肯定的频率发送
PING
音讯发送给筛选进去的实例,用于检测实例状态以及替换彼此的信息。PING
音讯中封装了发送者本身的状态信息、局部其余实例的状态信息、Slot 与实例映射表信息。 - 实例接管到
PING
音讯后,响应PONG
音讯,音讯蕴含的信息跟PING
音讯一样。
集群之间通过 Gossip
协定能够在一段时间之后每个实例都能获取其余所有实例的状态信息。
所以在有新节点退出,节点故障,Slot 映射变更都能够通过 PING
,PONG
的音讯流传实现集群状态在每个实例的流传同步。
Gossip 音讯
发送的音讯构造是 clusterMsgDataGossip
构造体组成:
typedef struct {char nodename[CLUSTER_NAMELEN]; //40 字节
uint32_t ping_sent; // 4 字节
uint32_t pong_received; // 4 字节
char ip[NET_IP_STR_LEN]; //46 字节
uint16_t port; // 2 字节
uint16_t cport; // 2 字节
uint16_t flags; // 2 字节
uint32_t notused1; // 4 字节
} clusterMsgDataGossip;
所以每个实例发送一个 Gossip
音讯,就须要发送 104 字节。如果集群是 1000 个实例,那么每个实例发送一个 PING
音讯则会占用 大概 10KB。
除此之外,实例间在流传 Slot 映射表的时候,每个音讯还蕴含了 一个长度为 16384 bit 的 Bitmap
。
每一位对应一个 Slot,如果值 = 1 则示意这个 Slot 属于以后实例,这个 Bitmap 占用 2KB,所以一个 PING
音讯大概 12KB。
PONG
与PING
音讯一样,一发一回两个音讯加起来就是 24 KB。集群规模的减少,心跳音讯越来越多就会占据集群的网络通信带宽,升高了集群吞吐量。
实例的通信频率
65 哥:码哥,发送 PING 音讯的频率也会影响集群带宽吧?
Redis Cluster 的实例启动后,默认会每秒从本地的实例列表中随机选出 5 个实例,再从这 5 个实例中找出一个最久没有收到 PING 音讯的实例,把 PING 音讯发送给该实例。
65 哥:随机抉择 5 个,然而无奈保障选中的是整个集群最久没有收到 PING 通信的实例,有的实例可能始终没有收到音讯,导致他们保护的集群信息早就过期了,咋办呢?
这个问题问的好,Redis Cluster 的实例每 100 ms 就会扫描本地实例列表,当发现有实例最近一次收到 PONG
音讯的工夫 > cluster-node-timeout / 2
。那么就立即给这个实例发送 PING
音讯,更新这个节点的集群状态信息。
当集群规模变大,就会进一步导致实例间网络通信提早怎加。可能会引起更多的 PING 音讯频繁发送。
升高实例间的通信开销
- 每个实例每秒发送一条
PING
音讯,升高这个频率可能会导致集群每个实例的状态信息无奈及时流传。 - 每 100 ms 检测实例
PONG
音讯接管是否超过cluster-node-timeout / 2
,这个是 Redis 实例默认的周期性检测工作频率,咱们不会轻易批改。
所以,只能批改 cluster-node-timeout
的值:集群中判断实例是否故障的心跳工夫,默认 15 S。
所以,为了防止过多的心跳音讯占用集群宽带,将 cluster-node-timeout
调成 20 秒或者 30 秒,这样 PONG
音讯接管超时的状况就会缓解。
然而,也不能设置的太大。都则就会导致实例产生故障了,却要期待 cluster-node-timeout
时长能力检测出这个故障,影响集群失常服务、
总结
「码哥字节」不跟风不扯淡,助力程序员成长。
《Redis 系列》至今已公布 7 篇,每一篇「码哥字节」都消耗大量精力,精益求精。确保每一篇都给读者带来价值,让大家失去真正的晋升。
- 哨兵集群实现故障主动转移,然而当数据量过大导致生成 RDB 工夫过长。而 Fork 执行的时候会阻塞主线程,因为数据量过大导致阻塞主线程过长,所以呈现了 Redis 响应慢的表象。
- 应用 Redis Cluster 集群,次要解决了大数据量存储导致的各种慢问题,同时也便于横向拓展。在面向百万、千万级别的用户规模时,横向扩大的 Redis 切片集群会是一个十分好的抉择。
- 集群的整个数据库被分为 16384 个槽(slot),数据库中的每个键都属于这 16384 个槽的其中一个,集群中的每个节点能够解决 0 个或最多 16384 个槽。
- Redis 集群节点采纳 Gossip 协定来播送本人的状态以及本人对整个集群认知的扭转。
- 客户端连贯到集群候任何一个实例后,实例会将哈希槽与实例映射信息发送给客户端,客户端将信息保留,用于将 key 定位到对应的节点。
- 集群并不能有限减少,因为集群通过
Gossip
协定流传集群实例信息,所以通信频率是限度集群大小的次要起因,次要能够通过批改cluster-node-timeout
调整频率。
原创不易,如果感觉文章不错,心愿读者敌人点赞、珍藏和分享。