1.scan前言
当我们使用 keys * 或 hgetall 进行查询的时候会进行堵塞,导致 redis 整体不可用(因为redis是单线程的),而使用 scan 命令则不会。
从Redis v2.8开始,SCAN命令已经可用,它允许使用游标从keyspace中检索键。
对比KEYS命令,虽然SCAN无法一次性返回所有匹配结果,但是却规避了阻塞系统这个高风险,从而也让一些操作可以放在主节点上执行。
2.SCAN相关命令
- SCAN相关命令包括SSCAN 命令、HSCAN 命令和 ZSCAN 命令,分别用于集合、哈希键及有序集等
- SCAN 命令用于迭代当前数据库中的数据库键。
- SSCAN 命令用于迭代集合键中的元素。
- HSCAN 命令用于迭代哈希键中的键值对。
- ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。
因为 SCAN 、 SSCAN 、 HSCAN 和 ZSCAN 四个命令的工作方式都非常相似, 要记住:
SSCAN 命令、 HSCAN 命令和 ZSCAN 命令的第一个参数总是一个数据库键。
而 SCAN 命令则不需要在第一个参数提供任何数据库键 —— 因为它迭代的是当前数据库中的所有数据库键。
3.基本用法:
命令格式:
SCAN cursor [MATCH pattern] [COUNT count]
命令解释:scan 游标 MATCH <返回和给定模式相匹配的元素> count 每次迭代所返回的元素数量
SCAN命令是增量的循环,每次调用只会返回一小部分的元素。所以不会有KEYS命令的坑(key的数量比较多,一次KEYS查询会block其他操作)。
SCAN命令返回的是一个游标,从0开始遍历,到0结束遍历。
通过scan中的MATCH <pattern> 参数,可以让命令只返回和给定模式相匹配的元素,实现模糊查询的效果
示例:
scan 0 match DL* count 5
sscan myset 0 match f*
f
返回值解释:
SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一个包含两个元素的 multi-bulk 回复:
- 回复的第一个元素是字符串表示的无符号 64 位整数(游标)
SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
- 回复的第二个元素是另一个 multi-bulk 回复
这个 multi-bulk 回复包含了本次被迭代的元素。
4、注意
SCAN命令不能保证每次返回的值都是有序的,另外同一个key有可能返回多次,不做区分,需要应用程序去处理。
SCAN 命令返回的每个元素都是一个数据库键。
SSCAN 命令返回的每个元素都是一个集合成员。
HSCAN 命令返回的每个元素都是一个键值对,一个键值对由一个键和一个值组成。
ZSCAN 命令返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。
5、Jedis实现
/**
* @Description: 实现hscan dowhile形式
* @Author: zongx
* @Date: 2020/3/6
* @Param: pattern
* @return java.util.List<java.lang.String>
*/
public Map<String,String> hscan(final String key,final String pattern) {
return execute(new JedisAction<Map<String,String>>() {
@Override
public Map<String, String> action(Jedis jedis) {
// 游标初始值为0
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.match(pattern);
scanParams.count(Integer.MAX_VALUE);
Map<String, String> results = new HashedMap();
do {
ScanResult<Map.Entry<String, String>> hscanResult =
jedis.hscan(key, String.valueOf(cursor), scanParams);
for (Map.Entry<String, String> en : hscanResult.getResult()) {
results.put(en.getKey(),en.getValue());
}
//获取游标位置,若大于0,则代表还有数据,需要继续迭代
cursor = hscanResult.getStringCursor();
} while (Integer.parseInt(cursor) > 0);
return results;
}});
}
/**
*scan,while形式
* @param pattern
* @param count
* @return
*/
public List<String> scan(final String pattern, final int count) {
return execute(new JedisAction<List<String>>() {
@Override
public List<String> action(Jedis jedis) {
ScanParams params = new ScanParams();
params.match(pattern);
params.count(count);
String cursor = "0";
List<String> results = new ArrayList<>();
while (true) {
ScanResult scanResult = jedis.scan(cursor, params);
List<String> elements = scanResult.getResult();
if (elements != null && elements.size() > 0) {
results.addAll(elements);
}
cursor = scanResult.getStringCursor();
if ("0".equals(cursor)) {
break;
}
}
return results;
}
});
}
代码还可以参考:
https://www.xttblog.com/?p=3635
工具类在下载中心可以下载