当我们遇到一个大数据量存储情况时,有两种解决方案:
纵向扩容:升级单个redis实例的配置,包括增加内存容量、磁盘容量,以及使用更高的cpu配置。
纵向扩容:简单直接。也存在一些问题:当使用RDB对数据进行持久化时,如果数据量增加,需要的内存也会增加,主线程fork子进程就可能阻塞;第二个问题就是硬件和成本的限制。
横向扩容:横向增加当前redis实例的数量,降数据分配到多个实例中;
现实情况下,在面对千万级甚至亿级别的流量的时候,很多大厂都是在千百台的实例节点组成的集群上进行流量调度、服务治理的。所以,使用集群模式,是业内广泛采用的模式。
下面简单说下横向扩容也就是Redis的Cluster集群模式以及Codis。
我们使用集群模式首先会想到两个问题:
1.数据如何在多个实例之间分布的
2.客户端怎么确定要访问的数据在哪个实例上
3.hash槽重新分配时,接到请求如何处理
简单的说Redis Cluster采用哈希槽(Hash Slot 简称slot)来处理数据和实例之间的映射关系,在Redis Cluster方案中,一个集群共有16384(为什么是这个数)个slot,每个键值对都会根据他的key,被映射到一个slot上。每个key通过CRC16算法计算一个16bit值然后对16384取模,来决定放到哪个slot,每个节点负责维护一部分槽以及slot所映射的键值数据。
公式:slot = CRC16(key) & 16383
为什么使用hash槽概念?(不使用一致性hash)参考:浅析一致性hash和hash槽_hash槽和一致性hash_蓝桉未与的博客-CSDN博客
很容易添加或者删除节点:
比如如果我想新添加个节点D, 我需要从节点 A, B, C中转移部分槽到D上即可.如果我像移除节点A,需要将A中得槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
既然我们知道了数据如何存储到hash槽的,那客户端又是如何知道要访问的数据在哪一个实例上的呢?
在定位键值对数据时,所处的slot可以通过计算得到的,这个计算可以在客户端发送请求时来执行,但是还需要知道这个slot在哪个实例上。
当客户端与集群建立连接后,实例就会把slot的分配信息发给客户端,同时redis的实例会把自己的slot信息向其他相连接的实例同步,来完成slot分配信息的扩散,所以每个实例就会知道所有的slot分配信息了。
客户端接收到信息后,会吧hash槽信息缓存到本地,当客户端请求键值对是,会先计算键所对应的hash槽,然后就可以给相应的实例发送请求了。
在集群中实例和hash槽的对应关系并不是一成不变的,常见的变化有以下两个:
1.新增或者删除实例,Redis需要重新分配hash槽
2.负载均衡,redis需要把hash槽重新分配一遍
实例之间是可以相互传递消息的,获得最新的哈希槽分配信息,但是客户端并不能实时获取这些信息,这就会导致,缓存的分配信息和最新的不一致,当这种情况出现时,会怎么办呢?
Redis Cluster提供了一种重定向机制,就是指,当客户端给一个实例发送读写操作时,这个实例上并没有相应的数据,客户端还要再给新的实例再发送一次。
重定向机制包含两种:moved重定向和ask重定向。
moved重定向:
ask重定向:
在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移。当槽及槽中数据正在迁移时,客服端请求目标节点时,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端。
两者的区别:moved迁移了,ask:迁移中
思考:
16384(为什么是这个数)?作者的回答:https://github.com/antirez/redis/issues/2576
1.如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
在消息头中,最占空间的是 myslots[CLUSTER_SLOTS/8]。当槽位为65536时,这块的大小是: 65536÷8=8kb因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。
2.redis的集群主节点数量基本不可能超过1000个。
如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
3.槽位越小,节点少的情况下,压缩率高。
Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。而16384÷8=2kb,怎么样,神奇不!
Codis Proxy (codis-proxy):接受客户端请求,并转发给codis-server。
客户端连接的 Redis 代理服务,codis-proxy 本身实现了 Redis 协议,表现得和一个原生的 Redis 没什么区别 (就像 Twemproxy), 对于一个业务来说,可以部署多个 codis-proxy, codis-proxy 本身是无状态的。
Codis Manager (codis-config):Codis 的管理工具,支持包括,添加 / 删除 Redis 节点,添加 / 删除 Proxy 节点,发起数据迁移等操作. codis-config 本身还自带了一个 http server, 会启动一个 dashboard, 用户可以直接在浏览器上观察 Codis 集群的运行状态.
Codis Redis (codis-server):进行了二次开发的redis实例,其中增加了额外的数据结构,支持数据迁移操作,主要负责处理具体的数据读写请求。
ZooKeeper:存放数据路由表和 codis-proxy 节点的元信息,codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy.
处理流程
在Codis集群中,一个数据保存在哪个实例上,也是通过逻辑槽来完成的。
1.Codis集群中一共1024个Slot,我们可以手动分配,也可以让Codis dashboard自动分配
2.如何确定key放到哪个slot上呢?当客户端读写数据时,会使用CRC32算法计算key的哈希值,然后对1024取摸,对应slot的编号,就知道分配到哪个实力上了。
我们把Slot和server的映射关系成为数据路由表,分配好路由表后,会把路由表发送给Codis proxy,codis proxy 会把路由表缓存到本地,当客户端收到请求后,直接查询本地路由表,完成正确的转发;同时,也会把路由表保存在zookeeper中。
同时,数据路由表在每个实例也会进行通信传递的,每个实例上都会保存一份,数据路由发生变化时,就需要在所有实例间通过网络消息进行传递。如果实例较多的话,就会消耗较多的集群网络资源。
欢迎大家访问:http://mumuxi.chat/
http://mumuxi.chat/articles/149 (资源分享# ai一些免费的GPT 4)