文章目录
1 背景
Redis是知名的、应用广泛的NoSQL数据库,在转转也是作为主要的非关系型数据库使用。我们主要使用Codis来管理Redis分布式集群,但随着Codis官方停止更新和Redis Cluster的日益完善,转转也开始尝试使用Redis Cluster,并选择Lettuce作为客户端使用。但是在业务接入过程中发现,使用Lettuce访问Redis Cluster的mget、mset等Multi-Key命令时,性能表现不佳。
2 分析原因
2.1 现象
业务在从Codis迁移到Redis Cluster的过程中,在Redis Cluster和Codis双写了相同的数据。结果Codis在比Redis Cluster多一次连接proxy节点的耗时下,同样是mget获取相同的数据,使用Lettuce访问Redis Cluster还是比使用Jeds访问Codis耗时要高,于是我们开始定位性能差异的原因。
2.2 定位问题
2.2.1 Redis Cluster的架构设计
导致Redis Cluster的mget性能不佳的根本原因,是Redis Cluster在架构上的设计导致的。Redis Cluster基于smart client和无中心的设计,按照槽位将数据存储在不同的节点上
如上图所示,每个主节点管理不同部分的槽位,并且下面挂了多个从节点。槽位是Redis Cluster管理数据的基本单位,集群的伸缩就是槽和数据在节点之间的移动。
通过CRC16(key) % 16384
来计算key属于哪个槽位和哪个Redis节点。而且Redis Cluster的Multi-Key操作受槽位限制,例如我们执行mget,获取不同槽位的数据,是限制执行的:
2.2.2 Lettuce的mget实现方式
lettuce对Multi-Key进行了支持,当我们调用mget方法,涉及跨槽位时,Lettuce对mget进行了拆分执行和结果合并,代码如下:
public RedisFuture<List<KeyValue<K, V>>> mget(Iterable<K> keys) {
//将key按照槽位拆分
Map<Integer, List<K>> partitioned = SlotHash.partition(codec, keys);
if (partitioned.size() < 2) {
return super.mget(keys);
}
Map<K, Integer> slots = SlotHash.getSlots(partitioned);
Map<Integer, RedisFuture<List<KeyValue<K, V>>>> executions = new HashMap<>();
//对不同槽位的keys分别执行mget
for (Map.Entry<Integer, List<K>> entry : partitioned.entrySet()) {
RedisFuture<List<KeyValue<K, V>>> mget = super.mget(entry