一 留意细节
摸清数据规模,即问清楚边界
方法1: 利用keys pattern
KEYS pattern:查找所有符合给定模式pattern的key
eg: keys a* 找出所有以a开头的key
使用keys对线上业务的影响
- KEYS指令一次性返回所有匹配的key
- 键的数量过大会使服务卡顿
方法2:SCAN cursor match pattern count countnum
eg:scan 0 match *a count 10 从游标为0
- 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
- 以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历
- 不保证每次执行都返回某个给定数量的元素,支持模糊查询
- 一次返回的数量不可控,只能大概率符合count参数
二.关于scan的使用
下面提供两个我自己包装的scan,一个是hash里模糊取key,另外一个是直接string key的模糊取
@Override
@SuppressWarnings("unchecked")
public <T> Map<String, T> hScan(String key, String pattern) {
final Map<String, T> scanResult = Maps.newHashMap();
try (Cursor<Map.Entry<Object, Object>> cursor = template.opsForHash().scan(key, ScanOptions.scanOptions()
.count(Integer.MAX_VALUE)
.match(pattern)
.build())) {
while (cursor.hasNext()) {
final Map.Entry<Object, Object> entry = cursor.next();
scanResult.put(String.valueOf(entry.getKey()), (T) entry.getValue());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return scanResult;
}
@Override
public Set<String> scan(String pattern) {
return template.execute((RedisCallback<Set<String>>) connection -> {
Set<String> keysTmp = Sets.newHashSet();
try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
.count(Integer.MAX_VALUE)
.match(pattern)
.build())) {
while (cursor.hasNext()) {
keysTmp.add(new String(cursor.next()));
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return keysTmp;
});
}
三.关于scan的坑
- COUNT 选项只是对增量式迭代命令的一种提示(hint),不能代表返回的个数,它只是限定服务器单次遍历的字典槽位数量(约等于)。
- count要根据扫描数据量大小而定,Scan虽然无锁,但是也不能保证在超过百万数据量级别搜索效率;count不能太小,网络交互会变多,count要尽可能的大。在搜索结果集1万以内,建议直接设置为与所搜集大小相同