前言
很多业务都可能出现同时操作大量Key的情况,比如要同时获取多个用户的信息。
由于 Redis 数据量和访问量的持续增长,造成需要添加大量节点做水平扩容,导致键值分布到更多的节点上,批量操作通常需要从不同节点上获取,相比于单机批量操作只涉及到一次网络操作,Redis Cluster环境下的批量操作会涉及到多次网络时间。大家都知道Redis的性能瓶颈其实不是CPU,而是网络,所以我们要针对这种情况做出优化,尽可能少的减少网络请求的次数。
优化思路
我们以Redis单机批量获取 n 个字符串Key为例,有三种实现方法:
- 客户端 n 次 get:n 次网络 + n 次 get 命令本身。
- 客户端 1 次 pipeline get:1 次网络 + n 次 get 命令本身。
- 客户端 1 次 mget:1 次网络 + 1 次 mget 命令本身。
我们可以发现,在单个节点的情况下,使用mget是性能最好的方法。
下面我们将结合 Redis 集群的一些特性对四种分布式的批量操作方式进行说明。
实践
1.串行IO
由于 n 个 key 是比较均匀的分布在 Redis 集群的各个节点上,因此无法使用 mget 命令一次性获取,所以通常来讲要获取 n 个 key 的值,最简单的方法就是逐次执行 n 次 get 操作, 很明显这种操作时间复杂度较高,它的网络次数是 n,很显然这种方案不是最优的,但是实现起来比较简单:
List<String> serialMGet(List<String> keys) {
// 结果集
List<String> values = new ArrayList<String>();
// n次串行get
for (String key : keys) {
String value = jedisCluster.get(key);
values.add(value);
}
return values;
}
2.归并IO
以 Redis Cluster 为例,Redis Cluster 使用 CRC16 算法计算出散列值,再取对 16384 的余数就可以算出 slot 值,smart 客户端会保存 slot 和节点的对应关系,有了这两个数据就可以对将属于同一个节点的 key 进行归并,得到每个节点的 key 子列表,之后对每个节点执行 mget 或者 pipeline 操作,它的网络次数是 node 的个数,整个过程下图所示,很明显这种方案比第一种要好很多,但是如果节点数足够多,还是有一定的性能问题。
Map<String, String> serialIOMget(List<String> keys) {
//