Spring自定义Redis的scan命令总结

自定义redis的scan命令模糊查找key

情景

主要情景,Spring boot 2.*使用自定义的scan方法代替RedisTemplate.keys(pattern)方法

代码

/**
     * scan
     * @param matchKey 要匹配的key的模糊表达式,例如 hpfm:lov:*
     * @param count 步进值,过小效率会低一些,尽量与数据级匹配些,此处默认1000
     *              private static final int SCAN_COUNT = 1000;
     * @return 所匹配到的key的Set集合
     */
private Set<String> scan(String matchKey, Integer count) {
        Set<String> keys = new HashSet<>();
        try {
            keys = redisHelper.getRedisTemplate().execute((RedisCallback<Set<String>>) connection -> {
                Set<String> keysTmp = new HashSet<>();
                Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(matchKey).count(count).build());
                while (cursor.hasNext()) {
                    keysTmp.add(new String(cursor.next()));
                }
                return keysTmp;
            });
        } catch (Exception e) {
            logger.info("redis cluster not support scan:" + e);
            keys = clusterScan(matchKey, count);
        }
        return keys;
    }
/**
     * 支持单机、主从、哨兵、集群
     * @param matchKey 要匹配的key的模糊表达式,例如 hpfm:lov:*
     * @param count 步进值,过小效率会低一些,尽量与数据级匹配些,此处默认1000
     *              private static final int SCAN_COUNT = 1000;
     * @return 所匹配到的key的Set集合
     */
    private Set<String> clusterScan(String matchKey, Integer count) {
        return redisHelper.getRedisTemplate().execute((RedisConnection connection) -> {
            Set<String> keySet = new HashSet<>();
            //定义起始游标,获取lettuce原生引用,定义scan参数
            ScanCursor scanCursor = ScanCursor.INITIAL;
            RedisKeyAsyncCommands commands = (RedisKeyAsyncCommands) connection.getNativeConnection();
            ScanArgs scanArgs = ScanArgs.Builder.limit(count).match(matchKey);
            try {
                do {
                    //最少scan一次,当返回不为空时将扫描到的key添加到统一key列表中
                    KeyScanCursor<byte[]> keyScanCursor = (KeyScanCursor) commands.scan(scanCursor, scanArgs).get();
                    if (keyScanCursor != null) {
                        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(keyScanCursor.getKeys())) {
                            keyScanCursor.getKeys().forEach(b -> keySet.add(new String(b)));
                        }
                        scanCursor = keyScanCursor;
                    } else {
                        scanCursor = ScanCursor.FINISHED;
                    }
                } while (!scanCursor.isFinished());
            } catch (Exception e) {
                throw new CommonException("redisClient scanKey fail patternKey:[{}]", matchKey, e);
            }
            return keySet;
        });
    }

说明

一开始,我使用的是第一个方法,结果在redis集群环境中翻车了。基于此,我在本地搭建了主从,哨兵模式redis环境,验证第一个方法是ok的,也搭建了集群环境,验证第一个方法抛出异常,不支持,所以才有了第二个方法。

此次只是使用方面的总结,至于原理、lettuce的底层代码这些可以百度,个人也还需要去学习。

上一篇:Phoenix原理 | Phoenix查询计划剖析


下一篇:Java学习总结(二)——Scanner类的使用和随机数产生