自定义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的底层代码这些可以百度,个人也还需要去学习。