redis使用keys()模糊查询导致全量扫描,长链接,阻塞线程

问题描述:

        在使用java开发时,需要查询redis,但是key是一个不固定的值,只有一个固定的前缀。如:"testKey:",原策略使用keys方法获取redis库中所有符合条件的key,代码如下:        

Set<String> keys = redisUtil.keys("testKey:*");

此方法能够获取到所有前缀为"testKey:"的键集合,然后可以通过get(key)获取到相应的value。但是此方法会造成一下问题:

1、keys()方法是全量扫描的 ,每次都会扫描整个redis库

2、长链接,因为要全量扫描,那么当redis库中键值很多是,这个时间会非常非常长

3、阻塞线程,redis是单线程的,每次访问都需要抢夺权柄,当一个链接持有权柄时,其他的链接都只能排队,等它用完释放权柄后再重新抢夺权柄;那么当一个链接长时间持有权柄时,其它的访问链接一直抢不到权柄,一直排队,随着链接的增多,时间会越来越长,严重可能导致服务无法响应。

解决方法:

 1、使用 scan 方法

redis的scan方法同样支持模糊查询,但与keys方法的机制不同,keys方法是拿到权柄做全量扫描,直到把redis所有键扫描完才释放权柄;而scan方法虽然也做全量扫描,但是它可以设置每次扫描的数量(或者叫槽位),例如每次扫描1000个,那么当scan方法扫描到1000个之后,就会释放权柄,然后再重新抢夺,再次查询1000个,直到扫描完。

    public Set<String> scanMatch(String matchKey) {
        Set<String> keys = new HashSet<>();
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
        Iterable<RedisClusterNode> redisClusterNodes = clusterConnection.clusterGetNodes();
        Iterator<RedisClusterNode> iterator = redisClusterNodes.iterator();
        while (iterator.hasNext()) {
            RedisClusterNode next = iterator.next();
            Cursor<byte[]> scan = clusterConnection.scan(next, ScanOptions.scanOptions().match(matchKey).count(100000).build());
            while (scan.hasNext()) {
                keys.add(new String(scan.next()));
                System.out.println("线程:" + Thread.currentThread().getName() + ":当前key数量为:" + keys.size());
            }
//            boolean unFind = true;
//            while (scan.hasNext() && unFind) {
//                keys.add(new String(scan.next()));
//                if (keys.size() > 10) {
//                    unFind = false;
//                }
//                System.out.println("线程:" + Thread.currentThread().getName() + ":当前key数量为:" + keys.size());
//            }
        }
        return keys;
    }

 但是,此方法同样存在问题。

当redis库中键值对特别多时,scan方法会重复多次,虽然可以设置查多少条后结束,但是速度同样会远远高于正常程序的运行速度,而且redis是一个快速查询的、毫秒级库,scan方法的查询时间可能达到几十秒甚至更长(这主要取决于redis库的大小),显然这对于有性能要求的程序是不太适用的。

2、使用集合方法(推荐)

使用一个固定的redis键,比如:testKeyArray,value设置为集合类型;将以上的键在set进redis中时,同时也将其放入testKeyArray这个键所对应的value中,该value可以是一个键的Set集合;这样就可以直接通过固定的key值(testKeyArray)获取由所有的键组成的Set集合,再去遍历这个Set集合,拿到每一个具体key,在通过key拿到对应的value。

        Set<String> keys = null;
        Map<String,String> map = new HashMap<>();
        //判断key是否存在
        if (redisUtil.hasKey(REDISKEY)){
            //获取key集合
            keys = (Set<String>) redisUtil.get(REDISKEY); 
            if(keys != null && keys.size() > 0){
                //遍历集合,拿到key,再拿到对应的value
                for(String key : keys){
                    String value = redisUtil.get(key);
                    map.put(key,value);
                }
            }
        }
3、外部表 

新建一张数据库表,用来存放redis的key,每次直接从表中读取key,再去通过key拿到对应的value,此方法会增强对数据库的依赖性。

具体使用哪种方式需视实际情况而定。

  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值