我是如何解决redis集群批量获取的效率问题的

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我是如何解决redis集群批量获取的效率问题的相关的知识,希望对你有一定的参考价值。

参考技术A 相信各位在使用redis集群的时候,对于redis集群中的批量操作都会有一个现象:使用redis集群进行批量获取数据的时候,效率总是不高,取一次数据要达到几百毫秒,当你操作的数据是百万级别的时候,你就会发现redis的读取效率压根就不能接受。接下来告诉大家如何进行了解

### redis集群的哈希槽

redis集群中内置了16384个哈希槽,当一个key值准备存储的时候,是先通过将key进行 crc16 校验,校验后的值对16384取值,得到的值就是该key所在的槽(slot);redis集群中,节点上的槽是连续的一段,因此通过我们计算key得到的slot,就能判断该key是在存储在哪个节点上的。

#### 如何判断redis集群中各个节点上的slot分布?

  使用命令 : cluster nodes  或者  cluster slots

#### 如何知道一个key值对应的槽

使用命令: cluster keyslots key

#### 提高效率的解决方案

因此,通过上面我们就可以知道key值存储对应的reids集群的节点,因此我们可以做以下处理:将你所需要的key按照槽的值进行分批,用单点连接的形式连接到某个redis节点上,批量取处于同一个节点上的key。

注意:

- 一定要用单点的形式进行连接,还是使用集群方式连接的话,就算是处于一个节点,效率也是没有提高的;

- redis集群单点连接的话,不能使用mget,因此mget只能取位于同一个 slot 上的,你可以使用pipeline进行事务处理;

### 一次具体的实现

目前我使用的语言的php,借鉴了

[crc16算法计算](https://www.php.net/manual/en/function.crc32.php)

这个方式得到的结果再进行 mod 16384(固定值),从而得到hash槽。

### 后记

  因为知道了我的这些key的hash值,同时我也需要知道这些key值对应的节点,因此可以通过redis的命令(cluster slots)从而可以动态的得到相应的节点以及节点的hash值;

Redis集群批量操作

Redis在3.0版正式引入了集群这个特性,扩展变得非常简单。然而当你开心的升级到3.0后,却发现有些很好用的功能现在工作不了了, 比如我们今天要聊的pipeline功能等批量操作。

Redis集群是没法执行批量操作命令的,如mget,pipeline等。这是因为redis将集群划分为16383个哈希槽,不同的key会划分到不同的槽中。但是,Jedis客户端提供了计算key的slot方法,已经slot和节点之间的映射关系,通过这两个数据,就可以计算出每个key所在的节点,然后使用pipeline获取数据。

/**
 * 根据key计算slot,
 * 再根据slot计算node,
 * 获取pipeline
 * 进行批量操作 
 */
public class BatchUtil {
    public static Map<String, String> mget(JedisCluster jc, String... keys){
        Map<String, String> resMap = new HashMap<>();
        if(keys == null || keys.length == 0){
            return resMap;
        }
        //如果只有一条,直接使用get即可
        if(keys.length == 1){
            resMap.put(keys[0], jc.get(keys[0]));
            return resMap;
        }
        
        //JedisCluster继承了BinaryJedisCluster
        //BinaryJedisCluster的JedisClusterConnectionHandler属性
        //里面有JedisClusterInfoCache,根据这一条继承链,可以获取到JedisClusterInfoCache
        //从而获取slot和JedisPool直接的映射
        MetaObject metaObject = SystemMetaObject.forObject(jc);
        JedisClusterInfoCache cache = (JedisClusterInfoCache) metaObject.getValue("connectionHandler.cache");
        //保存地址+端口和命令的映射
        Map<JedisPool, List<String>> jedisPoolMap = new HashMap<>();
        List<String> keyList = null;
        JedisPool currentJedisPool = null;
        Pipeline currentPipeline = null;
        
        for(String key : keys){
            //计算哈希槽
            int crc = JedisClusterCRC16.getSlot(key);
            //通过哈希槽获取节点的连接
            currentJedisPool = cache.getSlotPool(crc);
            //由于JedisPool作为value保存在JedisClusterInfoCache中的一个map对象中,每个节点的
            //JedisPool在map的初始化阶段就是确定的和唯一的,所以获取到的每个节点的JedisPool都是一样
            //的,可以作为map的key
            if(jedisPoolMap.containsKey(currentJedisPool)){
                jedisPoolMap.get(currentJedisPool).add(key);
            }else{
                keyList = new ArrayList<>();
                keyList.add(key);
                jedisPoolMap.put(currentJedisPool, keyList);
            }
        }
        
        //保存结果
        List<Object> res = new ArrayList<>();
        //执行
        for(Entry<JedisPool, List<String>> entry : jedisPoolMap.entrySet()){
            try {
                currentJedisPool = entry.getKey();
                keyList = entry.getValue();
                //获取pipeline
                currentPipeline = currentJedisPool.getResource().pipelined();
                for(String key : keyList){
                    currentPipeline.get(key);
                }
                //从pipeline中获取结果
                res = currentPipeline.syncAndReturnAll();
                currentPipeline.close();
                for(int i=0; i<keyList.size(); i++){
                    resMap.put(keyList.get(i), res.get(i)==null ? null : res.get(i).toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
                return new HashMap<>();
            }
        }
        return resMap;
    }
}

 

以上是关于我是如何解决redis集群批量获取的效率问题的的主要内容,如果未能解决你的问题,请参考以下文章

图形验证码如何解决集群中使用问题

如何批量删除redis中keys

redis集群如何解决重启不了的问题

redis集群如何解决重启不了的问题

redis数据迁移

关于redis批量获取数据pipeline