项目场景:

提示:这里简述项目相关背景:

例如:项目场景:查平台的支付方式


问题描述

用来redis的scan模糊匹配的方式,查询key,在redis的key大于百万级的key的时候,会导致查询非常缓慢

package com.hznt.yeahgo.cms.portal.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * @author wangwenping
 * @description redis helper
 * @date 2021/5/14
 */
@Slf4j
public class RedisHelper {

    /**
     * scan 实现
     *
     * @param redisTemplate
     * @param pattern       表达式
     * @param consumer      对迭代到的key进行操作
     */
    public static void scan(RedisTemplate redisTemplate, String pattern, Consumer<byte[]> consumer) {
        //pattern="*"+pattern+"*";
        //String finalPattern = pattern;
        redisTemplate.execute((RedisConnection connection) -> {
            try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count(1000000).match(pattern).build())) {
                cursor.forEachRemaining(consumer);
                return null;
            } catch (IOException e) {
                log.error("redis scan 发生异常 {}", e);
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 获取符合条件的key
     *
     * @param pattern 表达式
     * @return
     */
    public static List<String> keys(RedisTemplate redisTemplate, String pattern) {
        List<String> keys = new ArrayList<>();
        scan(redisTemplate, pattern, item -> {
            //符合条件的key
            String key = new String(item, StandardCharsets.UTF_8);
            keys.add(key);
        });
        return keys;
    }
}

``   String resourceKey = cmsResourceByResourceKeyRequest.getResourceKey();
        String patternAll = CacheKey.getResourceKey(resourceKey, "*" + cmsResourceByResourceKeyRequest.getPlatform() + "*", "0.0.0");
        List<String> keys = RedisHelper.keys(redisTemplate, patternAll);






# 原因分析:
方法1:使用KEYS [pattern]:查找所有符合给定模式pattern的key

使用keys [pattern]指令可以找到所有符合pattern条件的key,但是keys会一次性返回所有符合条件的key,所以会造成redis的卡顿,假设redis此时正在生产环境下,使用该命令就会造成隐患,另外如果一次性返回所有key,对内存的消耗在某些条件下也是巨大的。

例:

keys test* //返回所有以test为前缀的key
方法2:使用SCAN cursor [MATCH pattern] [COUNT count]

cursor:游标
MATCH pattern:查询key的条件
count:返回的条数
SCAN是一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。SCAN以0作为游标,开始一次新的迭代,直到命令返回游标0完成一次遍历。此命令并不保证每次执行都返回某个给定数量的元素,甚至会返回0个元素,但只要游标不是0,程序都不会认为SCAN命令结束,但是返回的元素数量大概率符合count参数。另外,SCAN支持模糊查询。

例:

SCAN 0 MATCH test* COUNT 10 //每次返回10条以test为前缀的key
用游标和模糊匹配找key的方式,在key非常多的时候,内存消耗也会很严重,导致查询慢

---

# 解决方案:
>把这些相关的key,用String的方式或者hash或者listd的形式存储,用java代码来去查找匹配
    /**
     * 平台放最后
     * @param key
     * @return
     */
  public static  String getLastKeys(String  key,String appVersion,String platform){
      String resourceKey = new StringBuilder(ResourceConstant.toClientSourceKey).append(":")
              .append(key)
              .append(":").append("appVersion").append(":").append(appVersion).append(":").append("platform").append(":").append(platform).toString();
      return resourceKey;
  }//获取版本号和平台对应的key 	iOS&Android&MiniProgram&H5&FlutterAndroid&FultterIos	2.0.0的形式
String ishasKey = CacheKey.getLastKeys(resourceKey, appVersion, platform);
        log.info("要去截取的key"+ishasKey);
        String lastPlatform = ishasKey.substring(ishasKey.lastIndexOf(":") + 1);
        log.info("截取的后面的{}",lastPlatform);
        //截取前面的
        String beforePlateform = ishasKey.substring(0, ishasKey.lastIndexOf(":"));
        log.info("截取的前面的{}",beforePlateform);
        List<String> resourceResponseList = JSON.parseArray(cmsData, String.class);
        //拿出全部的key
        if (!CollectionUtils.isEmpty(resourceResponseList)) {
          log.info("redis资源位key的数量为{}",resourceResponseList.size());
            for (String key : resourceResponseList) {
                //将平台切割出来
                String lastPlatform1 = key.substring(key.lastIndexOf(":") + 1);
                //截取前面的
                String beforePlateform1 = key.substring(0, key.lastIndexOf(":"));
                if (beforePlateform1.equals(beforePlateform) && lastPlatform1.contains(lastPlatform)) {
                    finalKey = beforePlateform1 +":"+ lastPlatform1;
                    break;

                }

            }
         log.info("要查的最终匹配的key{}",finalKey);


        }

        return finalKey;