Redis Cluster实现原理(一)

4,291 阅读9分钟

Redis sentinel虽然解决了Redis的自动故障转移,但是真正使用的还是单机。当并发量很高的时候,会遇到内存、并发、存储等的瓶颈,这就需要使用Redis的分布式架构来达到负载均衡。

Redis分布式方案

Redis Sharding

Redis Sharding可以说是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。这样,客户端就知道该向哪个Redis节点操作数据。这是一种客户端分区的实现。客户端分片是把分片的逻辑放在Redis客户端实现,通过Redis客户端预先定义好的路由规则,把对Key的访问转发到对应的Redis实例上进行。

在这里插入图片描述

  • 优点:所有的逻辑都是可控的,不依赖于第三方分布式中间件,配置简单,服务端Redis之间无关联。
  • 缺点:客户端无法动态增删服务节点,客户端需要自行维护分发逻辑,可维护性差。

Codis

Codis是一个分布式的Redis解决方案(前豌豆荚团队开源),对于上层的应用来说,连接Codis Proxy和连接原生的Redis Server没有明显的区别(不支持的命令列表),上层应用可以像使用单机的Redis一样使用,Codis底层会处理请求的转发,不停机的数据迁移等工作,所有后边的一切事情,对于前面客户端来说是透明的,可以简单的认为后边连接是一个内存无限大的Redis服务。这是一种基于中间件的代理方案,要实现Codis的高可用需要部署多个Codis实例。

ff

Codis 将所有的 key 默认划分为 1024 个槽位(slot),它首先对客户端传过来的 key 进行 crc32 运算计算哈希值,再将 hash 后的整数值对 1024 这个整数进行取模得到一个余数,这个余数就是对应 key 的槽位。每个槽位都会唯一映射到后面的多个 Redis 实例之一,Codis 会在内存维护槽位和 Redis 实例的映射关系。多个Codis实例之间槽位关系需要通过Zookeeper来维护,所以这又增加了运维的负担。

  • 优点:实现了上层 Proxy 和底层Redis的高可用,数据分片和自动平衡,提供命令行接口和 RESTful API,提供监控和管理界面,可以动态添加和删除Redis节点。
  • 缺点: 部署架构和配置复杂,增加了运维代价。不支持事务和部分命令,增加了代理层导致性能有所降低。由于是非官方的集群方案,所以永远要比Redis官方更慢,新功能要等很久才能与官方同步。

Redis Cluster

Redis 3正式推出了官方集群技术,解决了多Redis实例协同服务问题。Redis Cluster可以说是服务端Sharding分片技术的体现,即将键值按照一定算法合理分配到各个实例分片上,同时各个实例节点协调沟通,共同对外承担一致服务。但实现原理与客户端sharding分片有所不同,redis cluster引入了槽(类似Codis,但cluster划分出了16384个槽),将所有的数据对应到槽中,然后每个Redis节点管理一部分槽。

在这里插入图片描述

  • 优点:无中心节点(所有Redis节点都是对等的节点,同步数据使用的是Gossip协议),数据按照槽存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,当节点数量改变时,只需要动态更改节点负责的槽就行,这对于客户端来说是透明的。不需要依赖中间件,运维成本低。
  • 缺点:严重依赖 Redis-trib 工具,缺乏监控管理,Failover节点的检测过慢,Gossip协议传播消息到最终一致性有一定的延迟。

数据分区规则

要将整个数据集划分到多个节点上,让每个节点负责一部分的数据,就需要使用数据分区规则。常用的分区规则是哈希分区和顺序分区。

  • 哈希分区的特点是离散度好、数据分布与业务无关但无法顺序访问。
  • 顺序分区的特点是离散度易倾斜、数据分布与业务相关但可以顺序访问(主要用于大数据分析的数据库中)。

Redis Cluster采用的是哈希分区规则,常用的哈希分区规则也有几种:固定哈希分区、一致性哈希分区、虚拟槽哈希分区。

固定哈希分区

根据特定的字段(Redis中就是使用键)使用哈希函数计算出Hash值,然后根据节点的数量N取模,来决定将数据映射到哪一个节点中。这种方式优点就是规则设置和实现都很简单,缺点就是扩容或收缩节点会涉及到很多数据的迁移。

在这里插入图片描述

一致性哈希分区

一致性哈希可以很好的解决稳定性问题,可以将所有的存储节点排列在首尾相接的Hash环上(环的大小为2 ^ 32),key在计算Hash后会 按顺时针方向找到最近的存储节点存放数据。而当新增或减少节点时,仅影响该节点在Hash环上顺时针方向的下一个节点。

一致性hash

当集群需要扩容时,只会影响环上顺时针方向的下一个节点(会给该节点减少负载)。如下图:

在这里插入图片描述

新增的node5为node4分担了一部分负载,node1到node5之间的key中的数据将会往node5中迁移(如果是用作缓存的话就直接失效),但是对其它节点没有任何影响。

当集群需要缩减时,减少一个节点,也只会影响顺时针方向的下一个节点(这个节点将承接移除节点的所有负载)。

在这里插入图片描述

移除node1,那么node1之前的所有数据将会往node5迁移(如果是缓存会直接失效),node5将要承接node1之前的所有负载。但是对于集群中的其他节点还是没有任何影响。

通过上面可以发现,一致性哈希分区规则在集群扩容或缩减时并不能达到负载均衡的目的(除非每次增加一倍或减去一半),所以一般分布式系统都会采用虚拟节点(虚拟槽)对一致性哈希进行改进。

虚拟槽哈希分区

虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希算法(Redis使用的是CRC16算法)把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。 槽是集群内数据管理和迁移的基本单位,每个节点都负责一定量的槽,先通过hash函数将键映射到槽中,然后将数据存储在该槽所对应的节点。这样就解耦了节点和数据的键之间的关系(键不是直接映射到节点上,而是映射到槽上),简化了节点的扩容和收缩。

虚拟槽哈希

通过虚拟槽哈希分区,可以轻松的实现节点的添加和删除。如果我想添加一个新节点Node4,则只需要将一些哈希槽从节点Node1,Node2,Node3移到新节点Node4。类似地,如果我想从集群中删除节点Node1,则只需将Node1所负责的哈希槽移动到Node2和Node3即可。当节点Node1上的槽全部移走时,就可以将其从群集中完全删除。

因为将哈希槽从一个节点移动到另一个节点不需要停止操作,所以添加和删除节点或更改节点持有的哈希槽不需要任何停机时间,这保证了集群的高可用。

Gossip协议

在分布式存储中需要提供维护节点元数据的机制,元数据就是指节点负责哪些数据(在Redis中就是指负责哪些槽)以及节点的状态。常用的元数据维护方式为集中式和P2P式。Redis Cluster采用的是P2P的Gossip协议。

这个协议的作用就像其名字表示的意思一样,非常容易理解,它的方式其实在我们日常生活中也很常见,比如电脑病毒的传播,森林大火,细胞扩散,传染病传播等等。

通信过程

集群中的每个节点会单独开通一个TCP通道,用于节点之间彼此的通信(端口号为节点端口加上10000)。

当有节点更新了状态(新节点加入、节点故障、主从角色变化、槽信息变化等)时,该节点会随机向周围几个节点传播消息,收到消息的节点会重复这个过程,最终集群中所有的节点都会收到该消息,达到集群状态最终一致的目的。

Gossip演示

优势

  • 可扩展性:网络可以允许节点的任意增加和减少,新增加的节点的状态最终会与其他节点一致。

  • 容错:网络中任何节点的宕机和重启都不会影响Gossip消息的传播,Gossip 协议具有天然的分布式系统容错特性。

  • 去中心化:Gossip协议不要求任何中心节点,所有节点都可以是对等的,任何一个节点无需知道整个网络状况,只要网络是连通的,任意一个节点就可以把消息散播到全网。

  • 最终一致性:Gossip协议实现信息指数级(logN)的快速传播,因此在有新信息需要传播时,消息可以快速地发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。

缺陷

  • 消息的延迟:由于Gossip协议中,节点只会随机向少数几个节点发送消息,消息最终是通过多个轮次的散播而到达全网的,因此使用 Gossip 协议会造成不可避免的消息延迟。不适合用在对实时性要求较高的场景下。

  • 消息冗余:Gossip协议规定,节点会定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,因此就不可避免的存在消息重复发送给同一节点的情况,造成了消息的冗余,同时也增加了收到消息的节点的处理压力。而且,由于是定期发送,因此,即使收到了消息的节点还会反复收到重复消息,加重了消息的冗余。