Redis 中 keys 命令带来的线上性能问题,怎么解决?

什么是keys命令?

keys官方文档 www.redis.cn/commands/ke…

KEYS pattern

查找所有符合给定模式pattern(正则表达式)的 key 。

时间复杂度为O(N),N为数据库里面key的数量。【参考文献】

例如,Redis在一个有1百万个key的数据库里面执行一次查询需要的时间是40毫秒 。

警告: KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 KEYS, 你最好还是用 Redis 的集合结构 SETS 来代替。

支持的正则表达模式:

h?llo 匹配 hello, hallo 和 hxllo
h*llo 匹配 hllo 和 heeeello
h[ae]llo 匹配 hello 和 hallo, 但是不匹配 hillo
h[^e]llo 匹配 hallo, hbllo, … 但是不匹配 hello
h[a-b]llo 匹配 hallo 和 hbllo

返回值

所有符合条件的key

Redis提供的所有API操作,相对于服务端方面都是one by one执行的,命令是一个接着一个执行的,不存在并行执行的情况。

虽说REDIS执行KEYS命令很快,但是由于生产环境上有近六百万KEY,以至于KEYS命令需要两三秒,这两三秒就会导致其它命令阻塞着,这在生产中是灾难性的;

redis提供了一个scan的命令

scan命令官方文档 www.redis.cn/commands/sc…

SCAN cursor [MATCH pattern] [COUNT count]

cursor 游标
[MATCH pattern] 需要正则匹配的字符串
count 扫描的key的个数

SCAN 命令及其相关的 SSCAN, HSCAN 和 ZSCAN 命令都用于增量迭代一个集合元素。

  • SCAN 命令用于迭代当前数据库中的key集合。
  • SSCAN 命令用于迭代SET集合中的元素。
  • HSCAN 命令用于迭代Hash类型中的键值对。
  • ZSCAN 命令用于迭代SortSet集合中的元素和元素对应的分值

栗子

redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2)  1) "key:611"
    2) "key:711"
    3) "key:118"
    4) "key:117"
    5) "key:311"
    6) "key:112"
    7) "key:111"
    8) "key:110"
    9) "key:113"
   10) "key:211"
   11) "key:411"
   12) "key:115"
   13) "key:116"
   14) "key:114"
   15) "key:119"
   16) "key:811"
   17) "key:511"
   18) "key:11"
redis 127.0.0.1:6379>

在上面这个例子中, 第一次迭代使用 0 作为游标, 表示开始一次新的迭代。第二次迭代使用的是第一次迭代时返回的游标 17 ,作为新的迭代参数 。

显而易见,SCAN命令的返回值 是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素。

在第二次调用 SCAN 命令时, 命令返回了游标 0 , 这表示迭代已经结束, 整个数据集已经被完整遍历过了。

以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历。

java代码实践一下

上面已经说了keys和scan的命令了,下面用jedis来进行实践一下,在本地编码自测:

<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.3</version>
        </dependency>
        <!--hutool工具类的jar包不是必须的,我是用习惯这个工具类了,你们有其他工具类直接去掉hutool即可-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.6.5</version>
        </dependency>



import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.thread.ExecutorBuilder;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class JedisService {
   

    public static Jedis getJedis(){
   
        return new Jedis("localhost",6379,1000000000);
    }

    volatile static Boolean testCycleSetExitFlag = Boolean.FALSE;
    /**
     * 初始化数据到redis
     */
    public static void initData() throws InterruptedException {
   
        TimeInterval timeInterval = DateUtil.timer();
        log.info("initData start");
        getJedis().set("111CCCCCCCCCCCCCC1111","1");//先把查找的key设置进去
        //初始化数据个数
        AtomicInteger count = new AtomicInteger(10000*2000);

        //开100个客户端去执行set命令
        Integer totalClientNum = 100;

        ExecutorService executor = ExecutorBuilder.create()
                .setCorePoolSize(10)
                .setMaxPoolSize(50)
                .setWorkQueue(new LinkedBlockingQueue<>(1024))
                .build();

        CountDownLatch countDownLatch = new CountDownLatch(totalClientNum);
        for (int i = 0; i < totalClientNum; i++) {
   
            executor.submit(()->{
   
                Jedis jedis = getJedis();
                while (true){
   
                    Integer crrentCount  = count.decrementAndGet();
                    if(crrentCount<=0){
   
                        break;
                    }
                    if(crrentCount%10000==0){
   
                        log.info(" 设置key的数量还剩:{}  已耗时:{}毫秒",crrentCount,timeInterval.interval());
                    }
                    jedis.set(UUID.randomUUID().toString().replaceAll("-",""),"1");
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        log.info("initData end  总耗时:{}毫秒",timeInterval.interval());
        executor.shutdown();
    }

    public static void main(String[] args) throws Exception {
   
        Jedis jedis = getJedis();
        System.out.println("服务正在运行: "+jedis.ping());
//        initData();//初始化数据
        log.info("现在redis服务有{}个key",jedis.dbSize());
        String pattern = "*CCCCCCCCCCCCCC*";

        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值