1 背景
Redis是知名的、应用广泛的NoSQL数据库。但是在集群环境下,我们发现mget的效率实在过低,很多时候甚至还不如for循环进行get、set。
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进行了拆分执行和结果合并,代码如下:
mget涉及多个key的时候,主要有三个步骤:
1、按照槽位 将key进行拆分;
2、分别对相同槽位的key去对应的槽位mget获取数据;
3、将所有执行的结果按照传参的key顺序排序返回。
所以Lettuce客户端,执行mget获取跨槽位的数据,是通过槽位分发执行mget,并合并结果实现的。而Lettuce基于Netty的NIO框架实现,发送命令不会阻塞IO,但是处理请求是单连接串行发送命令:
所以Lettuce的mget的key数量越多,涉及的槽位数量越多,性能就会越差。Codis也是拆分执行mget,不过是并发发送命令,并使用pipeline提高性能,进而减少了网络的开销。
3 解决