• 作者:老汪软件技巧
  • 发表时间:2024-09-16 15:02
  • 浏览量:

前言

大家好,我是田螺。

有位伙伴面试了字节,说有道题,他当时答不上,大家一起来看看:redis的cluster集群原理,客户端是怎样知道该访问哪个分片的。

我们应该怎样更好回答呢?可以分这几个维度

1. 为什么需要Redis Cluster

哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。

因此,Reids Cluster集群(切片集群的实现方案)应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它可以保存大量数据,即分散数据到各个Redis实例,还提供复制和故障转移的功能。

比如你一个Redis实例保存15G甚至更大的数据,响应就会很慢,这是因为Redis RDB 持久化机制导致的,Redis会fork子进程完成 RDB 持久化操作,fork执行的耗时与 Redis 数据量成正相关。

这时候你很容易想到,把15G数据分散来存储就好了嘛。这就是Redis切片集群的初衷。切片集群是啥呢?来看个例子,如果你要用Redis保存15G的数据,可以用单实例Redis,或者3台Redis实例组成切片集群,对比如下:

切片集群和Redis Cluster的区别:Redis Cluster是从Redis3.0版本开始,官方提供的一种实现切片集群的方案。

既然数据是分片分布到不同Redis实例的,那客户端到底是怎么确定想要访问的数据在哪个实例上呢?我们一起来看下Reids Cluster是怎么做的哈。

2. 客户端是怎样知道该访问哪个分片的?哈希槽

Redis Cluster方案采用哈希槽(Hash Slot),来处理数据和实例之间的映射关系。

一个切片集群被分为16384个slot(槽),每个进入Redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16bit的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。

集群中的每个节点负责一部分的哈希槽,假设当前集群有A、B、C3个节点,每个节点上负责的哈希槽数 =16384/3,那么可能存在的一种分配:

客户端给一个Redis实例发送数据读写操作时,如果这个实例上并没有相应的数据,会怎么样呢?MOVED重定向和ASK重定向了解一下哈

3. 实例上并没有相应的数据,会怎么样(MOVED重定向和ASK重定向)

在Redis cluster模式下,节点对请求的处理过程如下:

3.1 Moved 重定向

客户端给一个Redis实例发送数据读写操作时,如果计算出来的槽不是在该节点上,这时候它会返回MOVED重定向错误,MOVED重定向错误中,会将哈希槽所在的新实例的IP和port端口带回去。这就是Redis Cluster的MOVED重定向机制。流程图如下:

浏览器端访问是会话_集群的原理_

3.2 ASK 重定向

Ask重定向一般发生于集群伸缩的时候。集群伸缩会导致槽迁移,当我们去源节点访问时,此时数据已经可能已经迁移到了目标节点,使用Ask重定向可以解决此种情况。

4. 各个节点之间是怎么通信的呢(Gossip)

一个Redis集群由多个节点组成,各个节点之间是怎么通信的呢?通过Gossip协议!Gossip是一种谣言传播协议,每个节点周期性地从节点列表中选择 k 个节点,将本节点存储的信息传播出去,直到所有节点信息一致,即算法收敛了。

Gossip协议基本思想:一个节点想要分享一些信息给网络中的其他的一些节点。于是,它周期性的随机选择一些节点,并把信息传递给这些节点。这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。一般而言,信息会周期性的传递给N个目标节点,而不只是一个。这个N被称为fanout

Redis Cluster集群通过Gossip协议进行通信,节点之前不断交换信息,交换的信息内容包括节点出现故障、新节点加入、主从节点变更信息、slot信息等等。gossip协议包含多种消息类型,包括ping,pong,meet,fail等等

特别的,每个节点是通过集群总线(cluster bus) 与其他的节点进行通信的。通讯时,使用特殊的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是 16379。nodes 之间的通信采用特殊的二进制协议。

5. 集群内节点出现故障怎么办(故障转移)

Redis集群实现了高可用,当集群内节点出现故障时,通过故障转移,以保证集群正常对外提供服务。

redis集群通过ping/pong消息,实现故障发现。这个环境包括主观下线和客观下线。

6. 加餐:为什么Redis Cluster的Hash Slot 是16384?

对于客户端请求过来的键值key,哈希槽=CRC16(key) % 16384,CRC16算法产生的哈希值是16bit的,按道理该算法是可以产生2^16=65536个值,为什么不用65536,用的是16384(2^14)呢?

大家可以看下作者的原始回答:

Redis 每个实例节点上都保存对应有哪些slots,它是一个unsigned char slots[REDIS_CLUSTER_SLOTS/8]类型

既然为了节省内存网络开销,为什么 slots不选择用8192(即16384/2)呢?

8192 / 8(每个字节8bit) / 1024(1024个字节1kB) = 1kB,只需要1KB!可以先看下Redis 把 Key 换算成所属 slots 的方法

unsignedintkeyHashSlot(char*key,intkeylen){
ints,e;/*start-endindexesof{and}*/
for(s=0;sif(key[s]=='{')break;
/*No'{'?Hashthewholekey.Thisisthebasecase.*/
if(s==keylen)returncrc16(key,keylen)&0x3FFF;
/*'{'found?Checkifwehavethecorresponding'}'.*/
for(e=s+1;eif(key[e]=='}')break;
/*No'}'ornothingbetweeen{}?Hashthewholekey.*/
if(e==keylen||e==s+1)returncrc16(key,keylen)&0x3FFF;
/*Ifweareherethereisbotha{anda}onitsright.Hash
*whatisinthemiddlebetween{and}.*/
returncrc16(key+s+1,e-s-1)&0x3FFF;
}

Redis 将key换算成slots 的方法:其实就是是将crc16(key) 之后再和slots的数量进行与计算

这里为什么用0x3FFF(16383)来计算,而不是16384呢?因为在不产生溢出的情况下x % (2^n)等价于x & (2^n - 1)即x % 16384 == x & 16383

那到底为什么不用8192呢?

crc16 出来结果,理论上出现重复的概率为 1⁄65536,但实际结果重复概率可能比这个大不少,就像crc32 结果 理论上 1/40亿 分之一,但实际有人测下来10万碰撞的概率就比较大了。假如 slots 设置成 8192, 200个实例的节点情况下,理论值是 每40个不同key请求,命中就会失效一次,假如节点数增加到400,那就是20个请求。并且1kb 并不会比 2k 省太多,性价比不是特别高,所以可能 选16384会更为通用一点


上一条查看详情 +每日知识积累 Day 27
下一条 查看详情 +没有了