Redis命令之scan的用法和注意细节

背景

Redis提供了scan命令,用于增量迭代获取db里的key。

命令格式:SCAN cursor [MATCH pattern] [COUNT count]

其中SCANMATCHCOUNT为命令关键字;
cursor为游标,如果为0表示起始,每次执行命令会返回新的cursor,可用于下次命令的增量迭代;
pattern为模式,即匹配规则,如Match *表示匹配所有key,sys:*表示匹配sys:开头的所有key;
count为数量,表示每次命令返回多少个key;

注意:
MATCH pattern为可选参数,默认为Match *,匹配所有key;
COUNT count为可选参数,默认为Count 10,返回10个key;

特别注意:
COUNT count,是一种提示(hint),如Count 10表示期望返回10个key,大多数情况是有效的,但不一定100%返回10个key。

本地测试

执行以下Redis命令,新建5个key:

set a 1
set b 2
set c 3
set d 4
set e 5

查看当前db的key总数,执行dbszie
(integer) 5

查看所有key,执行keys *

1) "d"
2) "a"
3) "b"
4) "c"
5) "e"

注:
这里本机开发环境,且确定key总数很少,因此执行key *
线上环境、key总数较大时应谨慎操作,禁用key *避免命令耗时过长影响其它命令执行,用轻量的scan命令代替。

执行scan 0 match * count 1

1) "6"
2) 1) "b"

返回下个游标cursor为6,返回了1个key:b

根据返回的cursor,继续执行scan 6 match * count 1

1) "5"
2) 1) "d"
   2) "a"

返回下个游标cursor为5,返回了2个key:da

count 1返回了2个key,因此验证了Count count不一定100%返回count个key。

根据返回的cursor,继续执行scan 5 match * count 1

1) "7"
2) 1) "c"

返回下个游标cursor为7,返回了1个key:c

根据返回的cursor,继续执行scan 7 match * count 1

1) "0"
2) 1) "e"

返回下个游标cursor为0,返回了1个key:e
返回cursor为0,表示迭代结束,一共返回了5个key,符合预期。

项目实战

根据Redis的scan命令格式,定义java接口:

`List<String> scan(String keyPattern, int scanSize);`

其中:
keyPattern为命令里的pattern值,scanSizecount值;
返回List<String>表示命令迭代执行完成后所有的返回的key列表;

使用jedis依赖包,接口实现:

@Override
public List<String> scan(String keyPattern, int scanSize) {
    List<String> keys = new ArrayList<>();
    try (ShardedJedis shardedJedis = pool.getResource()) {
        try (Jedis jedis = shardedJedis.getAllShards().iterator().next()) {
            ScanParams scanParams = new ScanParams();
            scanParams.match(keyPattern);
            scanParams.count(scanSize);
            String cursor = ScanParams.SCAN_POINTER_START;
            while (true) {
                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
                cursor = scanResult.getStringCursor();
                List<String> list = scanResult.getResult();
                if (list.size() > 0) {
                    keys.addAll(list);
                }

                if ("0".equals(cursor)) {
                    break;
                }
            }
        }
    }
    return keys;
}

项目实战中,除了获取key,还有其它需求,如:
每次迭代获取一批key做一些自定义操作;
删除某个pattern的所有key;

接口定义:

void scan(String keyPattern, int scanSize, RedisKeyCallback callback);

void scan(String keyPattern, int scanSize, RedisBatchKeysCallback callback);

int scanAndDelete(String keyPattern, int scanSize, int deleteSize);

public interface RedisKeyCallback {
    void doCallback(Jedis jedis, String key);
}

public interface RedisBatchKeysCallback {
    void doCallback(Jedis jedis, List<String> keys);
}

接口实现:

@Override
public void scan(String keyPattern, int scanSize, RedisKeyCallback callback) {
    try (ShardedJedis shardedJedis = pool.getResource()) {
        try (Jedis jedis = shardedJedis.getAllShards().iterator().next()) {
            ScanParams scanParams = new ScanParams();
            scanParams.match(keyPattern);
            scanParams.count(scanSize);
            String cursor = ScanParams.SCAN_POINTER_START;
            while (true) {
                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
                cursor = scanResult.getStringCursor();
                List<String> list = scanResult.getResult();
                if (!CollectionUtils.isEmpty(list)) {
                    for (String key : list) {
                        callback.doCallback(jedis, key);
                    }
                }

                if ("0".equals(cursor)) {
                    break;
                }
            }
        }
    }
}

@Override
public void scan(String keyPattern, int scanSize, RedisBatchKeysCallback callback) {
    try (ShardedJedis shardedJedis = pool.getResource()) {
        try (Jedis jedis = shardedJedis.getAllShards().iterator().next()) {
            ScanParams scanParams = new ScanParams();
            scanParams.match(keyPattern);
            scanParams.count(scanSize);
            String cursor = ScanParams.SCAN_POINTER_START;
            while (true) {
                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
                cursor = scanResult.getStringCursor();
                List<String> list = scanResult.getResult();
                if (!CollectionUtils.isEmpty(list)) {
                    callback.doCallback(jedis, list);
                }

                if ("0".equals(cursor)) {
                    break;
                }
            }
        }
    }
}

@Override
public int scanAndDelete(String keyPattern, int scanSize, int deleteSize) {
    int count = 0;
    try (ShardedJedis shardedJedis = pool.getResource()) {
        try (Jedis jedis = shardedJedis.getAllShards().iterator().next()) {
            ScanParams scanParams = new ScanParams();
            scanParams.match(keyPattern);
            scanParams.count(scanSize);
            String cursor = ScanParams.SCAN_POINTER_START;
            List<String> deleteKeys = new ArrayList<>(deleteSize);
            while (true) {
                ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
                cursor = scanResult.getStringCursor();
                List<String> list = scanResult.getResult();
                if (list.size() > 0) {
                    for (int i = 0; i < list.size(); i++) {
                        count++;
                        String scanKey = list.get(i);
                        deleteKeys.add(scanKey);
                        if (deleteKeys.size() >= deleteSize || i >= list.size() - 1) {
                            jedis.del(deleteKeys.toArray(new String[0]));
                            deleteKeys.clear();
                        }
                    }
                }

                if ("0".equals(cursor)) {
                    break;
                }
            }
        }
    }
    return count;
}

参考

Redis命令文档 http://doc.redisfans.com/key/scan.html

posted @ 2024-08-12 22:41  cdfive  阅读(1046)  评论(0编辑  收藏  举报