vlambda博客
学习文章列表

Redis专题7:分布式方案

Redis 的分布式方案

如果要实现Redis的数据分片,一般会有三种方案:

  • 第一种是在客户端实现相关的逻辑,例如用取或者一致性哈希对key进行分片,查询和修改都先判断key 的路由。
  • 第二种是把做分片处理的逻辑抽取出来,运行一个独立的代理服务,客户端连接到这个代理服务,代理服务做请求的转发。
  • 第三种是基于服务端实现

客户端 Sharding

Jedis 客户端提供了 Redis Sharding 的方案,并且支持连接池


public class ShardingTest{ public static void main(String[] args){ JedisPoolConfig poolConfig = new JedisPoolConfig();  //Redis 服务器 JedisShardInfo shardInfo1 = new JedisShardInfo("127.0.0.1",6379); JedisShardInfo shardInfo2 = new JedisShardInfo("192.168.1.101",6379);  //连接池 List<JedisShardInfo> infoList = Arrays.asList(shardInfo1,shardInfo2); ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig,infoList);  ShardedJedis jedis = null; try{ jedis = jedisPool.getResource(); for(int i=0;i<100;i++){ jedis.set("k" + i,"" + i); } for(int i=0;i<100;i++){ System.out.println(jedis.get("k" + i)); } }finally{ if(jedis != null){ jedis.close(); } } }}

使用 ShardedJedis 之类的客户端分片代码的优势是配置简单,不依赖于其他中间件,分区的逻辑可以自己定义,比较灵活。但是基于客户端的方案,不能实现动态的服务增减,每个客户端需要自行维护分片策略,存在重复代码。

代理Proxy

Redis专题7:分布式方案

典型的代理分区方案有 Twitter 开源的 Twemproxy 和国内的豌豆荚开源的Codis

Twemproxy

Redis专题7:分布式方案


Twemproxy 的优点:比较稳定,可用性高

「不足」

1、出现故障不能自动转移,架构复杂,需要借助其他组件(LVS/HAProxy + Keepalived)实现HA

2、扩缩容需要修改配置,不能实现平滑的扩缩容(需要重新分布数据)

Codis

Codis 是一个代理中间件,用Go语言开发的

其功能:客户端连接Codis 跟连接Redis 没有区别


Codis Tewmproxy Redis Cluster
重新分片不需要重启 Yes No Yes
pipeline Yes Yes -
多key操作的hash tags{} Yes Yes Yes
重新分片时的多key Yes - No
客户端支持 所有 所有 支持Cluster协议的客户端

Redis专题7:分布式方案


分片原理:Codis 把所有的key分成N个槽,每个槽对应一个分组,一个分组对应于一个或者一组Redis 实例。Codis 对key 进行CRC32运算,得到一个32位的数字,然后模以N(槽的个数),得到余数,这个就是key 对应的槽,槽后面就是Redis 的实例。

Codis 的槽位映射关系时保存在Proxy 中的,如果要解决单点的问题,Cdois 也要做集群部署,多个Codis 节点怎么同步槽和实例的关系呢?需要运行一个Zookeeper(或者etcd/本地文件)。

在新增节点的时候,可以为节点指定特定的槽位,Cdois 也提供了自动均衡的策略,Codis 不支持事务,其他的一些命令也不支持

不支持的命令:

https://github.com/CodisLabs/codis/blob/release3.2/doc/unsupported_cmds.md

获取数据原理(mget):在Redis 中的各个实例里获取到符合的key,然后再汇总到Codis中。

Codis是第三方提供的分布式解决方案,在官方的集群功能稳定之前,Codis也得到了大量的应用。

Redis Cluster

官方:https://reedis.io/topics/cluster-tutorial/

Redis Cluster 是在Redis3.0的版本正式推出的,用来解决分布式的需求,同时也可以实现高可用。跟Codis 不一样,它是去中心化的,客户端可以连接到任意一个可用节点。

数据分片有几个关键的问题需要解决:

1、数据怎么相对均匀地分片

2、客户端怎么访问到相应的节点和数据

3、重新分片的过程,怎么保证正常服务。

「架构」

Redis Cluster 可以看成是由多个Redis 实例组合成的数据集合。客户端不需要关注数据的子集到底是存储在哪个节点,只需要关注这个集合整体。

以3主3从为例,节点之间两两交互,共享数据分片、节点状态等信息

「数据分布」

Cluster 解决分片的问题,那么数据又是如何分布的呢?如果我们希望数据分布均匀的话,首先可以考虑哈希后取模

  • 哈希后取模

例如,hash(key)%N,根据余数,决定映射到哪一个节点,这种方式比较简单,属于静态的分片规则。但是一旦节点数量变化,新增或者减少,由于取模的N发生变化,数据需要重新分布。

为了解决这个问题,我们可以采用一致性哈希算法。

一致性哈希:

一致性哈希的原理,把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织。因为是环形空间,0 和 2^32 -1 是重叠的。

假设我们有4台机器要哈希环来实现映射(分布数据),我们先根据机器的命令或者IP计算哈希值,然后分布到哈希环中(红色圆圈)

Redis专题7:分布式方案


现在有4条数据或者4个访问请求,对key计算后,得到哈希环中的问题(绿色圆圈)。沿着哈希环顺时针好到的第一个Node,就是数据存储的节点。

Redis专题7:分布式方案


在这种情况下,新增了一个Node5节点,不影响数据的分布

Redis专题7:分布式方案


删除了一个节点Node4,只会影响相邻的一个节点

Redis专题7:分布式方案


一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,他只会影响到下一个相邻的节点,对于其他节点没有影响。

但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀的分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点。

比如:2个节点,5条数据,只有1条分布到Node 2 ,4条分布到Node1 ,分布不均匀

Redis专题7:分布式方案

但是如果Node1 设置了两个虚拟节点,Node2 也设置了两个虚拟节点

这时候有3条数据分布Node 1,1条数据分布到Node 2


  • Redis 虚拟槽分区

Redis 即没有用哈希取模,也没有一致性哈希,而是用虚拟槽来实现的。

Redis 创建了 16384 个槽(slot),每个节点 负责一定空间的slot。比如Node1负责 0-5460,Node2 负责 5461-10922,Node3 负责 10923-16383

Redis 的每个master 节点维护一个 16384 位(2048bytes = 2KB)的位序列,比如序列的第0位 是1 ,就代表第一个slot是它负责。

对象分布到Redis 节点上时,对key 用CRC16算法计算在 %16384,得到一个slot 的值,数据落到负责这个slot 的Redis节点上。

查看key 属于哪个slot :

redis> cluster keyslot a

注意:key 与slot 的关系时永远不会变的,会变的只有slot 和Redis 节点的关系

那么怎么让相关的数据落到同一个节点上呢?

比如有些 multi key 操作食不能跨节点的,如果要让某些数据分布到一个节点上,怎么办呢?

在key 里面加入 {hash tag}即可。Redis 在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。

set a{qs}a 1set a{qs}b 1set a{qs}c 1

「客户端的重定向」

如果客户端连接到一条服务器,但是相关数据不再当前节点上,又该如何处理呢?

比如在 6379 端口的 Redis 的 redis-cli进行操作

set qs 1

但是 服务端返回MOVED ,也就是 根据key计算 出来的 slot 不归 6379 端口管理,而是 6380 端口管理,服务端返回MOVED 告诉客户端去 6380 端口操作。

这个时候更换端口,用 redis-cli -p 6380操作,才会返回OK,或者用 ./redis-cli -c -p port 的命令(c代表cluster)。这样客户端需要连接两次。

Jedis 等客户端 会在本地维护一份 slot-- node 的映射关系,大部分时候不需要重定向,所以叫做 smart jedis(需要客户端支持)

数据迁移

思考一下:新增或者下线了Master 节点,数据应该怎么迁移呢(重新分配)呢?

因为key 和slot 的关系时永远不会变的,当新增了节点的时候,需要把原有的slot 分配给新的节点负责,并且把相关的数据迁移过来。

添加新节点(新增一个 6381):

redis-cli --cluster add-node 127.0.0.1:6381 127.0.0.1:6379

新增的节点没有哈希槽,不能分布数据,在原来的任意一个节点上执行:

redis-cli --cluster reshard 127.0.0.1:6379

输入需要分配的哈希槽的数量(比如500),和哈希槽的来源节点(可以输入 all 或者 id)

高可用和主从切换原理

当slave 发现自己的master 变为 FAIL 状态时,便尝试进行Failover,以期望成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程,其过程如下:

1、slave 发现自己的master 变为 FAIL

2、将自己记录的集群currentEpoch 加1,并广播 FAILOVER_AUTH_REQUEST 信息

3、其他节点收到该信息,只有master 响应,判断请求者的合法性,并发送 FAILOVER_AUTH_ACK ,对每一个epoch 只发送一次ack

4、尝试 failover 的slave 手机FAILOVER_AUTH_ACK

5、超过半数后变成新的Master

6、广播Pong 通知其他集群节点

Redis Cluster 既能够实现主从的角色分配,又能够实现主从切换,相当于集成了 Replication 和 Sentinal  的功能

总结

「优势」

1、无中心架构

2、数据按照slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布

3、可扩展性,可线性扩展到1000个节点,节点可动态添加或者删除

4、高可用性,部分节点不可用时,集群仍可用,通过增加Slave 做standby 数据副本,能够实现故障自动failover,节点之间通过 gossip协议交换状态信息,用投票机制完成slave 到Master 的角色提升。

5、降低运维成本,提高系统的扩展性和可用性

「劣势」

1、Client 实现复杂,驱动要求实现 Smart Client ,缓存slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。

2、节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout),被判断下线,这种failover 是没有必要的。

3、数据通过异步复制,不保证数据的强一致性。

4、多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。

敬请关注: