【Redis】高可用之三:集群(cluster)

本文是Redis系列第6篇,前5篇欢迎移步

【Redis】不卡壳的 Redis 学习之路:从十大数据类型开始入手_AQin1012的博客-CSDN博客关于Redis的数据类型,各个文章总有些小不同,我们这里讨论的是Redis 7.0,为确保准确,我们直接看官网。https://blog.csdn.net/aqin1012/article/details/130365083

【Redis】持久化机制详解:从RDB到AOF,你需要知道的一切_AQin1012的博客-CSDN博客持久化其实就4个单词:加强数据安全Redis支持两种不同的持久化机制,RDB和AOF。https://blog.csdn.net/aqin1012/article/details/130481261

【Redis】不卡壳的 Redis 学习之路:事务_AQin1012的博客-CSDN博客数据库中的事务是指在一次与数据库连接的会话中,所有的SQL语句,要么都成功,要么都失败。在Redis中,事务是指可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会被序列化,按顺序地串行执行而不会被其他命令插入开启:以MULTI开始一个事务入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面执行:由EXEC命令触发事务。https://blog.csdn.net/aqin1012/article/details/131474273?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22131474273%22%2C%22source%22%3A%22aqin1012%22%7D

【Redis】高可用之一:复制(replica)_AQin1012的博客-CSDN博客官网地址: https://redis.io/docs/management/replication/其实就是主从复制,master以写为主,slave以读为主当master数据变化的时候,自动将新的数据异步同步到其它slave数据库主机(master)能读能写,从机(slave)只能读无论主机已经写了多少数据,从机一旦启动,就会全部复制过来,后续主机写,从机跟配置文件🆚命令配置使用配置文件进行主从配置时,如果主机挂了,从机不会变化,还可以提供读的功能,等待主机恢复(重启后主从关系仍在)https://blog.csdn.net/aqin1012/article/details/131531792?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22131531792%22%2C%22source%22%3A%22aqin1012%22%7D

【Redis】高可用之二:哨兵(sentinel)_AQin1012的博客-CSDN博客哨兵会巡查监控后台master主机,查看是否存在故障,如果故障了,就会根据投票数自动将某一个从库转换为新主库,继续对外服务(解决复制的痛点)官方网址简单来讲,哨兵就是一种无人值守的运维机制3个哨兵自动监控和维护集群,不存放数据,只是"吹哨人"1主2从用于数据读取和存放主机宕机,从机数据还在会从剩下的两台主机中选出新的主机如果宕机的主机恢复,不会出现主机冲突,恢复的主机将变为新主机的从机。https://blog.csdn.net/aqin1012/article/details/131595489本文目录

什么是集群

能做啥

集群算法

槽位slot

分片

使用槽位+分片的优势

槽位的映射方案

哈希取余分区

缺点

一致性哈希算法分区

是什么

一致性Hash算法背景

3大步骤

算法构建一致性哈希环

服务器IP节点映射

key落到服务器的落键规则

Hash环的数据倾斜问题

优点

缺点

哈希槽分区

为什么Redis的槽数是16384?


什么是集群

官网地址

https://redis.io/docs/reference/cluster-spec/

由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展。每个数据集只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集

Redis集群可以支持多个Master,从哨兵到集群
哨兵
集群

能做啥

  • Redis集群支持多个Master,每个Master又可以挂载多个Slave
    • 读写分离
    • 支持数据的高可用
    • 支持海量数据的读写存储操作
  • 由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能
  • 客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可
  • 槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系

集群算法

槽位slot

Redis集群没有使用一致性hash,而是引入了哈希槽的概念。

集群的有16384个槽,有效地设置了16384个主节点的集群大小上限(但是官网建议的最大节点大小约为1000个节点),每个key通过CRC16 校验后对16384取模(HASH_SLOT = CRC16(key) mod 16384)来决定放置在哪个槽,集群的每个节点负责一部分hash槽(数据分片)。

当没有集群重新配置正在进行时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。

当集群稳定时,单个哈希槽将由单个节点提供服务(但是,服务节点可以有一个或多个副本,在网络分裂或故障的情况下替换它,并目可以用于扩展读取陈旧数据是可接受的操作)

分片

使用Redis集群时,我们会将存储的数据分散到多台Redis机器上,这就是分片。换句话说,就是集群中的每个Redis实例都被认为是整个数据的一个分片。

那么如何找到给定key的分片呢?

即对key进行CRC16(key)算法处理并通过总分片数量取模,然后,使用确定性哈希函数,这意味着给定的key将始终映射到同一个分片,因此可以推断将来读取特定key的位置

使用槽位+分片的优势

最大优势:方便扩缩容数据分派查找

这种结构很容易添加或者删除节点。

举个例子🌰:

如果我想新添加个节点D,就需要从节点A、B、C中得部分槽到D上

如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可

由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态

槽位的映射方案

slot槽位映射,一般业界有3种解决方案:哈希取余分区、一致性哈希算法分区和哈希槽分区,我们一一来介绍下他们。

哈希取余分区

公式 hash(key)%n

  • n 为机器台数

缺点

原来规划好的节点,进行扩容或者缩容就比较麻烦。不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化。

举个例子,2亿条记录就是2亿个k,v,单机不行就要考虑分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式hash(key)%N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

如果原来有3台,增加了1台,就会从Hash(key)%3变成Hash(key)%4。

此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。即某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一个词总结 -> 不一致

于是我们有了👇 ------ 一致性哈希算法分区

一致性哈希算法分区

是什么

一致性Hash算法背景

一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动映射问题(当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系),某个机器宕机了,分母数量改变了,自然取余数不行了。

3大步骤

算法构建一致性哈希环

将节点全部放到一个虚拟的圆环上,圆环上0在2^32处重合,其节点范围在0-2^32-1

一致性哈希算法必然有个哈希函数并按照算法产生哈希值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0=2^32),这样让它逻辑上形成了一个环形空间。它也是按照使用取模的方法,不过前面介绍的哈希取余分区取模法是对节点(服务器)的数量进行取模,而一致性哈希算法是对2^32取模(因此解决了取模数会变化的问题),简单来说,一致性哈希算法就是将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数的值空间为0~2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图:

整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4......直到2^32-1,也就是说0点左侧的第一个点代表2^32-1,0和2^32在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。

服务器IP节点映射

IP 或者主机名

节点映射

将集群中各个IP节点映射到环上的某一个位置。

将各个服务器使用Hash()进行次哈希,具体可以选择服务器的IP或主机名作为关键字,这样每台机器就能确定其在哈希环上的位置。举个例子,如下图4个节点NodeA、B、C、D,经过IP地址的哈希函数计算Hash(IP),使用IP地址哈希后在环空间的位置如下:

key落到服务器的落键规则

当我们需要存储一个kv键值对时,首先计算key的hash值(hash(key))将这个key使用相同的哈希函数计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针"行走",第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。

举个例子,如下图中Object A和Object 两个数据对象,经过哈希计算后,在环空间上的位置如下图所示:

根据一致性哈希算法,Object A会被定位到Node B上,Object B定位到Node D上

Hash环的数据倾斜问题

一致性哈希算法在服务节点太少时,容易因为节点分布不均匀 而造成数据倾斜 的问题**。**

即被缓存的对象大部分集中缓存在某一台服务器上,举个例子,如果只有两台服务器,就容易出现大部分数据都存在其中一个服务器上的情况,如下图所示:

其实,搞个环也就是为了在节点数目发生改变时,尽量少的迁移数据 ,将所有的节点排列在首尾相接的Hash环上,每个key在计算后会顺时针找到邻近的储存节点存放,而当有节点加入或者退出时仅影响该节点在Hash环上顺时针相邻的后续节点

优点

加入和删除只影响hash环中顺时针方向相邻的节点,对其他节点无影响

缺点

数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上 的,所以数据在进行存储时达不到均匀分布的效果。

这就是一致性哈希算法在节点较少时会有数据倾斜的问题,因此我们有了👇 ------ 哈希槽分区

哈希槽分区

为了解决一致性哈希算法数据倾斜的问题,产生了哈希槽分区

其实质就是一个数组,数组[0, 16383]形成哈希槽空间(共16384个槽位)

16384就是2^14-1

没有什么是加一层解决不了的,如果有,那就再加一层

解决均匀分配的问题,在数据和节点之间又加了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,就相当于节点上放的是槽,槽里放的是数据。

槽解决粒度的问题,相当于粒度调大了,便于数据的移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

一个集群只能有16384个槽,编号0-16383(0~2^14-1),这些槽会分配给集群中的所有主节点,分配策略没有要求。

集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取模,余数是几key就落入对应的槽里(HASH_SLOT=CRC16(key)mod 16384)。

以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

为什么Redis的槽数是16384?

Redis集群并没有使用一致性hash而是引入了哈希槽的概念。

Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置那个槽,集群的每个节点负责一部分哈希槽,但为什么哈希槽的数量是16384(2^14)个呢?

CRC16算法产生的哈希值有16bit,该算法可以产生2^16=65536个值,换句话说值是分布在0~65535之间,有更大的65536不用为什么只用16384呢?

我们看下作者的解释

https://github.com/redis/redis/issues/2576

The reason is:

  1. Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots.
  2. At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.

So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.

简单翻译下:

  1. 正常的心跳数据包带有节点的完整配置,可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。这意味着它们包含原始节点的插槽配置,该节点使用2k的空间和16k的插槽,但是会使用8k的空间(使用65k的插槽)。
  2. 同时,由于其他设计折衷,Redis集群不太可能扩展到1000个以上的主节点。

因此16k处于正确的范围内,以确保每个主机具有足够的插槽,最多可容纳1000个矩阵,但数量足够少,可以轻松地将插槽配置作为原始位图传播。请注意,在小型群集中,位图将难以压缩,因为当N较小时,位图将设置的slot/N占设置位的很大百分比。

(1)如果槽位为65536,发送心跳信总的消息头达8k,发送的心跳包过于庞大。

在消息头中最占空间的是myslots[CLUSTER_SL0TS/8],当槽位为65536时,这块的大小是:65536÷8÷1024=8kb;当槽位为16384时,这块的大小是:6384÷8÷1024=2kb

因为每秒钟,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的压缩率也会很低。

总结起来就是:适用、小巧、精干

相关推荐
只因在人海中多看了你一眼1 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
Dlwyz2 小时前
redis-击穿、穿透、雪崩
数据库·redis·缓存
工业甲酰苯胺4 小时前
Redis性能优化的18招
数据库·redis·性能优化
Oak Zhang7 小时前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存
门牙咬脆骨7 小时前
【Redis】redis缓存击穿,缓存雪崩,缓存穿透
数据库·redis·缓存
门牙咬脆骨7 小时前
【Redis】GEO数据结构
数据库·redis·缓存
墨鸦_Cormorant9 小时前
使用docker快速部署Nginx、Redis、MySQL、Tomcat以及制作镜像
redis·nginx·docker
Dlwyz12 小时前
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
数据库·redis·缓存
飞升不如收破烂~13 小时前
redis的List底层数据结构 分别什么时候使用双向链表(Doubly Linked List)和压缩列表(ZipList)
redis
吴半杯14 小时前
Redis-monitor安装与配置
数据库·redis·缓存