Redis 集群模式分析
- Cluster & Sharded说明
Cluster & Sharded说明
基础概念:
- 数据分布的控制权 归属上可分为:Server端模式、Client端模式(自己起的名字,用于表意)
1. Cluster模式 + 主从模式
Cluster模式为 Server端模式,及数据分布由Server控制
设计基本思路
在原有模型(Data <—> Instance)上抽象出中间层 Slot,即:Data <—> Slot <—> Instance,明显可知演化出了两种对应关系
- Data 与 Slot 之间的映射:通过算法进行对应:CRC16(key) & (16384 - 1)
- Slot 与 Instance 之间的映射:预分配(Range),集群默认虚拟化出16284个Slot
- Server:默认情况,将集群分成16384个Slot,并在启动集群时,为每个节点分配一段Range的Slot
- Client:连接集群中的各节点,并通过“slots”命令获取各节点对应的Slot Range,并进行cache
请求简化过程
- Client连接集群,获取各节点Slot分布情况,并于存储于SlotMappingCache中
- Client对Data(key, value)发起set请求
2.1. 首先对key进行 slot映射 计算(CRC16(key) & (16384 - 1))
2.2. 然后通过SlotMappingCache查询该slot对应的Instance,并获取connection
2.3. 发送set请求
2.3.1. 响应 – OK – done
2.3.2. 响应:JedisRedirectionException,刷新SlotMappingCache,并重复2.1、2.2、2.3的流程(由此可知,Server端有对key是否落在本Instance slots的校验逻辑)
其他说明
- 集群状态
- Cluster中各节点是通过广播来实现信息(如可用性等)同步的,并通过一致投票的方式来决定节点是否处于Failure状态,并采取后续行动。即没有类似ZK这样的统一管理角色(Sentinel这里不进行说明)
- 当出现一下情况时,Redis Cluster将进去Failure状态
- 某个Range完全不提供服务(Master + Slave 都不能提供服务)
- 半数以上的Master不能提供服务
- Server端模式
- Cluster模式为Server端模式,即可以通过Server来调整数据的分布情况,例如addNode、delNode、reShard等,都将改变数据的分布情况
- 在Cluster模式下,当数据分布发生变化后,Client通过JedisRedirectionException可以感知到数据分布的变化,并同步最新的数据分布映射关系
- 可能正是因为这方面的顾虑,所以JedisCluster并没有提供对 分布式操作 (mget & mset & Pipeline & Transaction)的支持
- 主从模式
- 通过Range分区,可以使各节点都参与服务,但是顾及到服务宕机的情况,不得不为每一台Master做一个Slave
- 同步方式,默认为异步sync,所以Redis官方 Cluster是不能很好的保证数据强一致性的,且主从切换时,可能会带来数据丢失;可以调整成同步sync,但是会影响服务性能
- 遗留问题
- Cluster模式下添加节点后,可能会导致Client无法识别新节点(观看源码后的猜测)
- 在发生Redirection异常时进行的renewClusterSlots中,并没有尝试通过命令来获取集群中的成员,即所有成员只包括初始化的那批,因此,可能无法识别到新节点
- slot重分配之后,数据的转移操作由谁负责?Server端?
- Cluster模式下添加节点后,可能会导致Client无法识别新节点(观看源码后的猜测)
2. Sharded模式 + 主从模式
Sharded模式为 Client端模式,及数据分布由Client控制
设计基本思路
Sharded模式采用的是ConsistentHash算法。
如果说Cluster模式是 “数据落槽” 的话,那么Sharded模式则是 “数据找槽”
因为Cluster模式下,对数据的Slot计算将会得到一个明确的Slot;但是在Sharded中,对数据的计算只能得到该数据对应的Index,接着还需要通过Index按照一定的方向去寻找离得最近的Slot。
在网上,针对ConsistentHash算法的介绍已经是一片一片了,这里我简单展示一下源码,不细聊该算法。
public class Sharded<R, S extends ShardInfo<R>>
private final Hashing algo; // MD5 算法
private TreeMap<Long, S> nodes; // Index与ShardInfo的映射关系,使用有序映射结构 -- TreeMap
private final Map<ShardInfo<R>, R> resources = new LinkedHashMap<ShardInfo<R>, R>(); // ShardInfo与Jedis的映射关系
public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) {
this.algo = algo;
this.tagPattern = tagPattern;
initialize(shards);
}
private void initialize(List<S> shards) {
nodes = new TreeMap<Long, S>();
for (int i = 0; i != shards.size(); ++i) {
// 获取一个节点
final S shardInfo = shards.get(i);
// 创建160个虚拟Slot,并存储映射关系至nodes<Long, ShardInfo>中
for (int n = 0; n < 160; n++) { nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo); }
// 存储shardInfo与Jedis至resources<ShardInfo, Jedis>
resources.put(shardInfo, shardInfo.createResource());
}
}
public S getShardInfo(byte[] key) {
// 计算key以得到index,并根据index获取大于等于该index的nodes
SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));
// 不存在大于等于index的nodes,返回nodes的第一个元素
if (tail.isEmpty()) { return nodes.get(nodes.firstKey()); }
return tail.get(tail.firstKey()); // 返回大于等于index的nodes中的第一个元素
}
}
很明显,Sharded模式下,数据的分布 由Client控制,即Server端根本不知道自己存的是哪一部分的数据,甚至可以说,Server端根本不知道自己只是存储了一部分的数据。
既然是这样,那么Sharded模式根本就不能正常的进行集群 扩容。其实 更准确的说,Sharded模式下,集群不能进行数据分布的调整。但是 如果能进行 “不调整数据分布的扩容” 那么还是可以实现集群扩容的。
Pre-Sharding设计
为实现扩容,在Sharded模式上,还需要添加一点要求,即Instance需要小且多(内存小,数量多)。
简单说明
不调整数据分布:即不调整每个Instance上所存放数据的范围。
在这种前提下,并且假设每个Instance的内存上限为100G,那么将会是这样的情况:
- 如果Instance只有2个,则集群扩容后的容量上限将为200G
- 如果Instance有12个,则集群扩容后的容量上限将为1200G
- 所以Instance的数量,将直接决定集群容量的上限
- 同时在集群建立初期,提供的内存总量将不会太大,如120G,那么由于Instance数量较多,所以各Instance的内存将较小
扩容过程
- 首先假设集群组成:12个Instance(1 to 12),各Instance大小为10G,集群总容量120G;物理节点2个(A、B),即6个Instance分配至一个物理节点。---- A(1 to 6)、B(7 to 12)
- 由于生产要求,需要进行集群扩容,添加一台物理机器C,则可以做如下规划:A(1 to 4)、B(7 to 10)、C(5、6、11、12)。
- 操作
3.1. 在C节点上启动4个Instance(提升maxmemory阈值),并分别设置成5、6、11、12的 从节点
3.2. 待主从同步完成之后,将 域名代理服务器 进行调整,使Client连接至新的Instance
3.3. 关闭原5、6、11、12Instance服务,释放资源
3.4. 调整其余各Instance maxmemory参数,实现 动态扩容(config set maxmemory) - 整个集群中各Instance就此全部扩容完毕
其他说明
- 正是因为Sharded模式下,数据的分布不会被调整,所以ShardedJedis是支持Pipeline的。但是仍然不支持mget & mset,因为,它们是一次性发送的请求至服务端的,然而,这些操作中对应的key可能并不分布在一起
- 感觉整体上Sharded模式好像比Cluster模式要好点,但是似乎没有必要使用ConsistentHash算法(个人感觉)