前提概要
为什么需要Redis集群?
- 在讲Redis集群架构之前,我们先简单讲下Redis单实例的架构,从最开始的一主N从,到读写分离,再到Sentinel哨兵机制,单实例的Redis缓存足以应对大多数的使用场景,也能实现主从故障迁移。
但是,在某些场景下,单实例存Redis缓存会存在的几个问题:
Redis主从架构+Sentinel仍存在问题
写并发的压力仍在
- Redis单实例读写分离可以解决读操作的负载均衡,但对于写操作,仍然是全部落在了master节点上面,在海量数据高并发场景,一个节点写数据容易出现瓶颈,造成master节点的压力上升。
海量数据的存储压力
- (内存容量的限制)Redis的最大缺点和局限性就在于内存存储数据,这样子对容量而言会有相当大的限制。
- (持久化和硬盘的限制)Redis单实例本质上只有一台Master作为存储,如果面对海量数据的存储,一台Redis的服务器就应付不过来了,而且数据量太大意味着持久化成本高,严重时可能会阻塞服务器,造成服务请求成功率下降,降低服务的稳定性。
针对以上的问题,Redis集群提供了较为完善的方案,解决了存储能力受到单机限制,写操作无法负载均衡的问题。
什么是Redis集群?
【Redis集群】是一种服务器Sharding(分片)技术,3.0版本开始正式提供。
Redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台Redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了Cluster集群模式,实现了Redis的分布式存储,也就是说每台 Redis 节点上存储不同的内容。
- (分片存储)Redis3.0加入了Redis的集群模式,实现了数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题。
- (指令转换)Redis集群采用去中心化的思想,没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的Redis节点。
- (主从和哨兵)Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点。
如上图所示,Redis集群可以看成多个主从架构组合起来的,每一个主从架构可以看成一个节点(其中,只有master节点具有处理请求的能力,slave节点主要是用于节点的高可用)
集群的数据分片
前面讲到,Redis集群通过分布式存储的方式解决了单节点的海量数据存储的问题,对于分布式存储,需要考虑的重点就是如何将数据进行拆分到不同的Redis服务器上。常见的分区算法有hash算法、一致性hash算法,关于这些算法这里就不多介绍。
如果要实现 Redis 数据的分片,我们有三种方案。
- (客户端负载)第一种是在客户端实现相关的逻辑,例如用取模或者一致性哈希对key进行分片,查询和修改都先判断key的路由。
- Jedis客户端提供了Redis Sharding的方案,并且支持连接池。
- Sharded 分片的原理?怎么连接到某一个Redis服务:提供了一致性hash和md5散列两种hash算法,默认使用一致性hash算法。
- 并且为了使得请求能均匀的落在不同的节点上,Sharded Jedis会使用节点的名称(如果节点没有名称使用默认名称)虚拟化出160个虚拟节点。也可以根据不同节点的weight,虚拟化出160,weight个节点。
- 当客户端访问redis时,首先根据key计算出其落在哪个节点上,然后找到节点的ip和端口进行连接访问。
- (中间代理负载)第二种是把做分片处理的逻辑抽取出来,运行一个独立的代理服务,客户端连接到这个代理服务,代理服务做请求的转发。
- 典型的代理分区方案有 Twitter 开源的 Twemproxy 和国内的豌豆荚开源的 Codis
- (服务端负载)第三种就是基于服务端实现。
普通hash算法
- 如果是希望数据分布相对均匀的话,可以考虑哈希后取模。
- 将key使用hash算法计算之后:hash(key)%N,根据余数,决定映射到那一个节点。
- 优点就是比较简单,属于静态的分片规则。但是一旦节点数量变化,新增或者减少,由于取模的 N 发生变化, 数据需要重新分布和迁移。
一致性hash算法
- 把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织。因为是环形空间,0 和 2^32-1 是重叠的(总共2^32个)。
- 先根据机器的名称或者 IP计算哈希值。
- 然后分布到哈希环中。查找时先根据key计算哈希值,得到哈希环中的位置。
- 最后顺时针找到第一个大于等于该哈希值的第一个Node,就是数据存储的节点。
- 优点是在加入和删除节点时只影响相邻的两个节点。
- 缺点是加减节点会造成部分数据无法命中
- 此外,针对于hash节点分散不均匀或者倾倒状态,采用一个节点分为多个虚拟节点做优化
- 所以一般用于缓存,而且用于节点量大的情况下,扩容一般增加一倍节点保障数据负载均衡。
哈希槽Slot算法
Redis 集群既没有用哈希取模,也没有用一致性哈希,而是用Hash槽来实现的。Redis集群创建了 16384 个槽(slot),每个节点负责一定区间的slot。
Redis集群的哈希槽的分区
- Redis集群中有16384(2^14)个哈希槽(槽的范围是 0 -16383,哈希槽),将不同的哈希槽分布在不同的Redis节点上面进行管理,也就是说每个Redis节点只负责一部分区间的哈希槽。
- 对数据进行操作的时候:
- 集群会对使用CRC16算法对key进行计算并对16384取模 (slot = CRC16(key)%16384) 。】
- 得到的结果就是Key-Value所放入的槽,通过这个槽值,去找对应的槽所对应的Redis节点。
- 然后直接到这个对应的节点上进行存取操作。
- 好处:(自动更新hash槽的映射数据关系)使用哈希槽的好处就在于可以方便的添加或者移除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。
- 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
- 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;
哈希槽分区的分析
集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
- 节点 A 包含 0 到 5460 号哈希槽
- 节点 B 包含 5461 到 10922 号哈希槽
- 节点 C 包含 10923 到 16383 号哈希槽
这种结构很容易添加或者删除节点。
- (1)如果我想新添加个节点 D , 我需要从节点 A, B, C 中得部分槽到 D 上。
- (2)如果我想移除节点 A ,需要将 A 中的槽移到 B 和 C 节点上,然后将没有任何槽的 A 节点从集群中移除即可。
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
查看 key 属于哪个 slot:
redis> cluster keyslot yan 复制代码
注意:key 与 slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系
(tips) 怎么让相关的数据落到同一个节点上
在key里面加入{hash tag}即可。Redis在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽
客户端重定向
客户端连接到哪一台服务器?访问的数据不在当前节点上,怎么办?
比如在 7291 端口的 Redis 的 redis-cli 客户端操作:
127.0.0.1:7291> set qs 1 (error) MOVED 13724 127.0.0.1:7293 复制代码
- 服务端返回 MOVED,也就是根据 key 计算出来的 slot 不归 7191 端口管理,而是 归 7293 端口管理,服务端返回 MOVED 告诉客户端去 7293 端口操作。
- 这个时候更换端口,用 redis-cli –p 7293 操作,才会返回 OK。这样客户端需要连接两次。或者用./redis-cli -c -p port 的命令(c 代表 cluster)自动重定向。
新增或下线了 Master 节点,数据怎么迁移(重新分配)?
- 因为 key 和 slot 的关系是永远不会变的,当新增了节点的时候,需要把原有的 slot 分配给新的节点负责,并且把相关的数据迁移过来。
- 新增的节点没有哈希槽,不能分布数据,在原来的任意一个节点上执行。
redis-cli --cluster reshard 127.0.0.1:7291 复制代码
- 槽的迁移与指派命令:
CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000 复制代码
输入需要分配的哈希槽的数量(比如 500),和哈希槽的来源节点(可以输入 all 或 者 id)
集群的主从复制模型
- **为了保证高可用,redis-cluster集群引入了主从复制模型,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点 **。
- 当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机了。如果主节点 A 和它的从节点 A1 都宕机了,那么该集群就无法再提供服务了。
- 默认情况下,redis集群的读和写都是到master上去执行的,不支持slave节点读和写,跟Redis主从复制下读写分离不一样,因为redis集群的核心的理念,主要是使用slave做数据的热备,以及master故障时的主备切换,实现高可用的。
- Redis的读写分离,是为了横向任意扩展slave节点去支撑更大的读吞吐量。而redis集群架构下,本身master就是可以任意扩展的,如果想要支撑更大的读或写的吞吐量,都可以直接对master进行横向扩展。
集群的特点
- redis集群内部的节点是相互通信的(PING-PONG机制),每个节点都是一个redis实例;内部使用二进制协议优化传输速度和带宽。
- 为了实现集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster有这么一个投票容错机制:如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法
- Redis集群是没有统一的入口的,客户端与 Redis 节点直连,不需要中间代理层,也不需要连接集群所有节点,即连接集群中任何一个可用节点即可。
- 客户端(client)连接集群的时候连接集群中的任意节点(node)即可。
- 那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法;
集群运行的要求
- Redis集群至少需要3个节点,因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以2个节点无法构成集群。
- 要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以Redis集群至少需要6台服务器。
集群的总结
优势
- 无中心架构。
- 数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布。
- 解耦数据和节点之间的关系,简化了扩容和收缩难度;
- 节点自身维护槽的映射关系,不需要客户端代理服务维护槽分区元数据
- 支持节点、槽、键之间的映射查询,用于数据路由,在线伸缩等场景.
3.可扩展性,可线性扩展到1000个节点(官方推荐不超过 1000 个)节点可动态添加或删除。
4.高可用性,部分节点不可用时,集群仍可用。通过增加Slave做 standby 数据副本,能够实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成 Slave到Master的角色提升。
5.降低运维成本,提高系统的扩展性和可用性。
不足
- Client 实现复杂,驱动要求实现 Smart Client,缓存 slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。
- 节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover是没有必要的。
- 数据通过异步复制,不保证数据的强一致性
- 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容 易出现相互影响的情况。