Redis —— 海量扫描Scan

本文详细介绍了Redis中scan命令的工作原理,包括其与keys命令的区别、count参数的实际含义、遍历顺序及在扩容缩容时的影响。同时,通过分析redisson源码展示了在实际应用中如何进行安全的scan操作,特别是在集群环境下的使用策略,强调了避免服务卡顿和大key扫描的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、scan扫描原理

二、scan测试


一、scan扫描原理

  如上图所示,需注意问题:

1. scan与keys:复杂度都是O(n),但scan的游标分步扫描。若是生产海量扫描,keys一次获取所有正则的key,造成服务卡顿,所以避免使用keys操作

2. scan命令中count:不是元素的数量,而是slot数量,从而导致每次分步遍历获取元素数量不同

3. 遍历顺序:高进位加法,意思是:扩容时,从高位进位,向右遍历。

4. 扩容缩容:每次扩容都是现有空间2倍,即:2^(n+1),如:110扩容后(0110、1110 _ 两者相邻)。遍历元素可能重复,尤其是扩容/缩容的情况下遍历

5. 对指定容器对象遍历:sscan、hscan、zscan

6. 扫描大key:易出现服务卡顿,redis内存大起大落

二、scan测试

下面代码是redisson的scan扫描,redis单例和集群都可以使用,若是集群扫描:每个node进行scan,结果集相加。

    @Override
    public Map<String, Object> scanRedisData(String key) {
        // 返回结果
        Map<String, Object> result = Maps.newHashMap();

        // 注意:scan扫描,不能使用keys
        Iterator<String> iterator = redissonClient.getKeys().getKeysByPattern("*" + key + "*", 1000).iterator();
        List<String> keys = Lists.newArrayList();
        while (iterator.hasNext()){
            keys.add(iterator.next());
        }

        // 返回scan的keys
        result.put("data", keys);
        result.put("count", keys.size());
        return result;
    }

 redisson源码分析:

step1:获取所有主从实例,即List<MasterSlaveEntry>

step2:每个主从实例scan,异步读取readAsync从节点数据

@Override
    public Iterable<String> getKeysByPattern(String pattern, int count) {
        List<Iterable<String>> iterables = new ArrayList<Iterable<String>>();
        // 获取集群的MasterSlaveEntry实例(主从),并遍历
        for (MasterSlaveEntry entry : commandExecutor.getConnectionManager().getEntrySet()) {
            Iterable<String> iterable = new Iterable<String>() {
                @Override
                public Iterator<String> iterator() {
                    // 每个MasterSlaveEntry实例进行scan
                    return createKeysIterator(entry, pattern, count);
                }
            };
            iterables.add(iterable);
        }
        // 所有实例结果集相加
        return new CompositeIterable<String>(iterables);
    }
    // 每个主从实例scan
    public RFuture<ListScanResult<Object>> scanIteratorAsync(RedisClient client, MasterSlaveEntry entry, long startPos,
                                                             String pattern, int count) {
        
        if (pattern == null) {
            return commandExecutor.readAsync(client, entry, StringCodec.INSTANCE, RedisCommands.SCAN, startPos, "COUNT",
                    count);
        }
        // 正则匹配查询
        // 采用从节点异步读取的scan
        return commandExecutor.readAsync(client, entry, StringCodec.INSTANCE, RedisCommands.SCAN, startPos, "MATCH",
                pattern, "COUNT", count);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值