Redis的集群解决分布式系统中负载均衡的原理

2022-09-29 14:41:17 浏览数 (2)

一 Redis单机缺陷

redis单机容量方面会有瓶颈,主从模式只能保证支撑更多读并发,但是slave和master的数据是一模一样的,也就是说master能存储多少数据,slave就也只能存储这么多数据。比如我们用的是32G的机器,但是我们要存100G东西,那怎么存呢?用单master的主从集群只能存32G,想存更多,只能扩展master,这就需要我们用到redis的集群策略了,我们可以以分布式的建立多个master来做这种集群结构,那么具体如何做redis的集群呢?请看下面.

二 集群方式

目前实现集群主要借助 redis cluster,redis集群模式,你可以做到在多台机器上,部署多个redis实例,每个实例存储一部分的数据,同时每个redis实例可以挂redis从实例,自动确保说,如果redis主实例挂了,会自动切换到redis从实例顶上来。

现在redis的新版本,大家都是用redis cluster的,也就是redis原生支持的redis集群模式。

三 redis集群的一般架构

redis cluster(多master 读写分离 高可用)

  • 支撑N个redis master node,每个master node都可以挂载多个slave node
  • 读写分离:对于每个master来说,写就写到master,然后读就从mater对应的slave去读
  • 高可用:因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master

我们只要基于redis cluster去搭建redis集群即可,不需要手工去搭建replication复制 主从架构 读写分离 哨兵集群 高可用

三 redis cluster

redis cluster功能

  • 自动将数据进行分片,每个master上放一部分数据
  • 提供内置的高可用支持,可以自主进行主备切换

在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加10000的端口号,比如16379

16379端口号是用来进行节点间通信的,也就是cluster bus的东西,集群总线。cluster bus的通信,用来进行故障检测,配置更新,故障转移授权

cluster bus用了另外一种二进制的协议,主要用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间

四 如何解决分布式系统中负载均衡的问题

4.1 有哪些算法?

4.1.1普通hash

弊端比较大,会导致非常严重的数据错乱问题,简单说下。 根据key进行普通hash,如果其中一个结点宕机了,会立马损失掉整个结点的数据,并且数据分布也错乱了,每次请求拿不到,导致大量请求打到mysql上。

比如ABC三个master分别接受对3取模后hash值为0,1,2的key值数据,如果B宕机了,首先B数据丢失了,其次,当请求再次取模是对2(现有机器数)取模,那么原来请求落到的redis结点可能也就变了,新的接受请求的结点并没有存储原来的数据,就会重新查mysql拿数据,如果此时有高并发,极可能发送雪崩问题。

4.1.2一致性hash

百度百科 在使用n台缓存服务器时,一种常用的负载均衡方式是,对资源o的请求使用hash(o)=o mod n来映射到某一台缓存服务器。当增加或减少一台缓存服务器时这种方式可能会改变所有资源对应的hash值,也就是所有的缓存都失效了,这会使得缓存服务器大量集中地向原始内容服务器更新缓存。因此需要一致哈希算法来避免这样的问题。 一致哈希尽可能使同一个资源映射到同一台缓存服务器。这种方式要求增加一台缓存服务器时,新的服务器尽量分担存储其他所有服务器的缓存资源。减少一台缓存服务器时,其他所有服务器也可以尽量分担存储它的缓存资源。 一致哈希算法的主要思想是将每个缓存服务器与一个或多个哈希值域区间关联起来,其中区间边界通过计算缓存服务器对应的哈希值来决定。(定义区间的哈希函数不一定和计算缓存服务器哈希值的函数相同,但是两个函数的返回值的范围需要匹配。)如果一个缓存服务器被移除,则它所对应的区间会被并入到邻近的区间,其他的缓存服务器不需要任何改变 [1] 。

上述问题和需求的解决方法→使用数据分片:按照某种规则去划分海量数据,分散存储在多个Redis服务器节点上
4.1 原理:
redis一致性哈希算法
  • 对2^32取模,将哈希值空间组织成虚拟的圆环
  • 用同样的Hash算法计算服务器的Hash值(可以用服务器IP或者主机名计算)确定在环上的位置
  • 用同样的Hash算法计算数据的Hash值,确定在环上的位置
  • 数据映射保存到在环上顺时针遇到最近的服务器
  • 这样即可将数据分散到不同服务器上面进行处理
    • 这样当一个服务器宕机了只会影响这台服务器于逆时针碰到的第一台服务器之间的数据,不会影响其他数据
    • 当新添加了一个结点服务器时也只会影响新服务器在Hash环上的位置到逆时针碰到的第一台服务器之间的数据 ,其他数据不会收到影响
  • 一致性Hash算法具有很高的容错性和可扩展性

图解一致性Hash算法

4.2一致性hash数据倾斜问题(也叫数据热点问题)

数据倾斜问题及解决

虚拟结点是随机分布的,这样会使原来少量服务器时候造成的数据分布不均匀变的均匀,原理如图

具体的生产环境实体落地情景是怎样的呢?
  • 首先取各个机器的特定值的hash值,比如mac地址hash值
  • 将hash值进行排序组成一个数组,如[15,20,55,106],分别对应m2,m3,m1,m4
  • 那么对要存的数据进行一个hash找到第一个数据的hash值大于机器值的机器,该数据就存到这个机器上

4.1.3 slot实现数据分布

一旦某个master宕机了,redis会快速将原来的hash slot分配到其他master上,当然这里避免不了的,会有一定的请求需要重新拿mysql数据

redis cluster有固定的16384个hash slot(2的14次方),我们会对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot

redis cluster中每个master都会持有部分slot,比如有3个master,那么可能每个master持有5000多个hash slot

hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去

移动hash slot的成本是非常低的

客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现;

为什么hash slot用的是16384而不是其他?老毛病了,总感觉这些特殊的数字有点意思(镜像问题:redis为什么每次容量都设置2的幂次方,为什么转红黑树的条件是链表长度为8即同一节点hash冲突次数为8?),果不其然,可以看看作者给的回答

  • 空间上 :正常的心跳数据包带有节点的完整配置,可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。 这意味着它们包含原始节点的插槽配置,这样节点16k的插槽会使用2k的空间,使用65k的插槽使用8k的空间。
  • 需求上:Redis集群不太可能扩展到1000个以上的主节点,因此16k处于正确的范围内,足以使每个主机具有足够的插槽。
  • 保存传递:另外Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩, 详细大家可以看 https://www.cnblogs.com/rjzheng/p/11430592.html https://blog.csdn.net/zalu9810/article/details/102797240 https://blog.csdn.net/hollis_chuang/article/details/103692187

五 Redis 结点之间内部通信

5.1 Redis 结点之间内部通信机制

redis cluster节点间采取gossip协议进行通信

分布式系统中结点之间主要有两种通讯机制

  • 集中式:好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力
  • gossip:跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的。好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后
流言协议Gossip

进程间使用流言协议即Gossip来同步信息,接收主服务器是否下线的信息,并使用投票协议来决定是否进行自动故障迁移以及选择哪个从服务器作为新的主服务器

5.1.1流言协议Gossip --在杂乱无章中寻求一致
  • 每个节点都随机地与对方通信,最终所有节点的状态达成一致
  • 种子节点定期随机向其他节点发送节点列表以及需要传播的消息
  • 不保证信息一定会传递给所有节点,但是最终会趋于一致

gossip协议包含多种消息,包括ping,pong,meet,fail,等等

  • meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信 redis-trib.rb add-node 其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群
  • ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据 每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新
    • ping消息深入 ping很频繁,而且要携带一些元数据,所以可能会加重网络负担 每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点 当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了 比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题 所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率 每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换 至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息
  • pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新
  • fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
5.2 开放端口 10000端口

每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号 10000,比如7001,那么用于节点间通信的就是17001端口

每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong

5.3结点间通讯交换的信息

故障信息,节点的增加和移除,hash slot信息,等等

六 面向集群的jedis内部实现原理

jedis是redis cluster的java client客户端,jedis cluster api

1)请求重定向

客户端可能会挑选任意一个redis实例去发送命令,每个redis实例接收到命令,都会计算key对应的hash slot

如果在本地就在本地处理,否则返回moved给客户端,让客户端进行重定向

cluster keyslot mykey,可以查看一个key对应的hash slot是什么

用redis-cli的时候,可以加入-c参数,支持自动的请求重定向redis-cli接收到moved之后,会自动重定向到对应的节点执行命令

2)计算hash slot

计算hash slot的算法,就是根据key计算CRC16值,然后对16384取模,拿到对应的hash slot

也可以手动指定solt:用hash tag可以手动指定key对应的slot,同一个hash tag下的key,都会在一个hash slot中,比如set mykey1:{100} v1和set mykey2:{100} v2,他们属于同一个tag,那么他们会路由到同一服务器上

3)hash slot查找

节点间通过gossip协议进行数据交换,就知道每个hash slot在哪个节点上

4)smart jedis
1.什么是smart jedis

上面我们也说了jedis一开始是基于客户端进行重定向很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点

所以大部分的客户端,比如java redis客户端,就是jedis,都是smart本地维护一份hashslot -> node的映射表,缓存,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向

2. JedisCluster的工作原理(含如果数据迁移了的数据寻找过程)
  • 在JedisCluster初始化的时候,就会随机选择一个node初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池
  • 每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点
    • 如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved(表示该hashslot已不在这个node了)
    • 如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存

重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException

jedis老版本,可能会出现在集群某个节点故障还没完成自动切换恢复时,频繁更新hash slot,频繁ping节点检查活跃,导致大量网络IO开销 jedis最新版本,对于这些过度的hash slot更新和ping,都进行了优化,避免了类似问题

3. hashslot迁移和ask重定向
    1. 如果hash slot正在迁移,那么会返回ask重定向给jedis
    1. jedis接收到ask重定向之后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以JedisCluster API收到ask是不会更新hashslot本地缓存

只有确定说,hashslot已经迁移完了,返回moved是会更新本地hashslot->node映射表缓存的

七 高可用性与主备切换原理

redis cluster的高可用的原理,几乎跟哨兵是类似的

1)判断节点宕机

如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机 如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown

在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail

如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail

2)从节点过滤

对宕机的master node,从其所有的slave node中,选择一个切换成master node

检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master

这个也是跟哨兵是一样的,从节点超时过滤的步骤

3)从节点选举

哨兵:对所有从节点进行排序,slave priority,offset,run id

redis cluster :每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前优先进行选举 所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master

从节点执行主备切换,从节点切换为主节点

4)三种集群模式的对比
  • 主从模式 可以实现读写分离,数据备份。但是并不是「高可用」的
  • 哨兵模式 可以看做是主从模式的「高可用」版本,其引入了 Sentinel 对整个 Redis 服务集群进行监控。但是由于只有一个主节点,因此仍然有写入瓶颈
  • Cluster 模式 去中心化,无主节点不仅提供了高可用的手段,同时数据是分片保存在各个节点中的,可以支持高并发的写入与读取。当然实现也是其中最复杂的。
八 redis架构演进

基于不同阶段redis的演进之路,到底为什么?

  • 单机版 Redis:最简版
  • 数据持久化:有备无患
  • 主从复制:多副本
  • 哨兵:故障自动转移
  • 分片集群:横向扩展

详细的可以看,如果了解每个阶段的发展可以起到融会贯通,huo'ran'kai https://mp.weixin.qq.com/s/WzbeCyOORPq2E3T43jfEjw

0 人点赞