目录

一、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);
    }
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐