Redis命令之scan的用法和注意细节
背景
Redis提供了scan
命令,用于增量迭代获取db里的key。
命令格式:SCAN cursor [MATCH pattern] [COUNT count]
其中SCAN
、MATCH
、COUNT
为命令关键字;
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:d
、a
;
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
值,scanSize
为count
值;
返回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