SpringBoot3 + Jedis5 + Redis集群 如何通过scan方法分页获取所有keys
背景:
由于需要升级老项目代码,从SpringBoot1.5.x 升级到 SpringBoot3.3.x,框架中引用的Jedis自动升级到了 5.x;正好代码中有需要获取Redis集群的所有keys的需求存在;代码就不适用了,修改如下:
POM
由于默认是lettuce,需要手动添加jedis客户端
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!-- 不依赖Redis的异步客户端lettuce -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
Jedis3.x写法
public Set<String> keys(String pattern) {
Set<String> set = new HashSet<>();
JedisClusterConnection jedisClusterConnection = (JedisClusterConnection) redisTemplate.getConnectionFactory().getClusterConnection();
//这里是获取jedispool的另外一种方式与上边的pipline可以对比下,两种方式都可以实现
Map<String, JedisPool> clusterNodes = jedisClusterConnection.getNativeConnection().getClusterNodes();
for (Map.Entry<String, JedisPool> entry : clusterNodes.entrySet()) {
//获取单个的jedis对象
Jedis jedis = entry.getValue().getResource();
// 判断非从节点(因为若主从复制,从节点会跟随主节点的变化而变化),此处要使用主节点, 从主节点获取数据
if (!jedis.info("replication").contains("role:slave")) {
Set<String> keys = getScan(jedis, pattern);
if (keys.size() > 0) {
Map<Integer, Set<String>> map = new HashMap<>(8);
//接下来的循环不是多余的,需要注意
for (String key : keys) {
// cluster模式执行多key操作的时候,这些key必须在同一个slot上,不然会报:JedisDataException:
int slot = JedisClusterCRC16.getSlot(key);
// 按slot将key分组,相同slot的key一起提交
if (map.containsKey(slot)) {
map.get(slot).add(key);
} else {
Set<String> set1 = new HashSet<>();
set1.add(key);
map.put(slot, set1);
}
}
for (Map.Entry<Integer, Set<String>> integerListEntry : map.entrySet()) {
set.addAll(integerListEntry.getValue());
}
}
}
}
return set;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Set<String> getScan(Jedis jedis, String key) {
Set<String> set = new HashSet<>();
//扫描的参数对象创建与封装
ScanParams params = new ScanParams();
params.match(key);
//扫描返回一百行,这里可以根据业务需求进行修改
params.count(1000);
String cursor = "0";
ScanResult scanResult = jedis.scan(cursor, params);
//scan.getStringCursor() 存在 且不是 0 的时候,一直移动游标获取
while (null != scanResult.getCursor()) {
//封装扫描的结果
set.addAll(scanResult.getResult());
if (!"0".equals(scanResult.getCursor())) {
scanResult = jedis.scan(scanResult.getCursor(), params);
} else {
break;
}
}
return set;
}
Jedis5.x写法
/**
* 获取符合条件的key
* @param pattern 表达式
* @return
*/
public Set<String> keys(String pattern) {
Set<String> set = new HashSet<>();
JedisClusterConnection jedisClusterConnection = (JedisClusterConnection) redisTemplate.getConnectionFactory().getClusterConnection();
//这里是获取jedispool的另外一种方式与上边的pipline可以对比下,两种方式都可以实现
Map<String, ConnectionPool> clusterNodes = ((JedisCluster) jedisClusterConnection.getNativeConnection()).getClusterNodes();// 看源码获取 key就是 host:port格式
Set<String> nodes = clusterNodes.keySet();
for (String node : nodes) {
HostAndPort nodeObj = HostAndPort.from(node);
try (Jedis jedis = new Jedis(nodeObj.getHost(), nodeObj.getPort())) { // try-with-resources可以自动关闭资源
String roleInfo = jedis.info("replication");
if (roleInfo.contains("role:master")) {
Set<String> keys = getScan(jedis, pattern);
if (keys.size() > 0) {
Map<Integer, Set<String>> map = new HashMap<>(8);
//接下来的循环不是多余的,需要注意
for (String key : keys) {
// cluster模式执行多key操作的时候,这些key必须在同一个slot上,不然会报:JedisDataException:
int slot = JedisClusterCRC16.getSlot(key);
// 按slot将key分组,相同slot的key一起提交
if (map.containsKey(slot)) {
map.get(slot).add(key);
} else {
Set<String> set1 = new HashSet<>();
set1.add(key);
map.put(slot, set1);
}
}
for (Map.Entry<Integer, Set<String>> integerListEntry : map.entrySet()) {
set.addAll(integerListEntry.getValue());
}
}
}
} catch (Exception e) {
logger.error("redis连接错误:", e);
}
}
return set;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Set<String> getScan(Jedis jedis, String key) {
Set<String> set = new HashSet<>();
//扫描的参数对象创建与封装
ScanParams params = new ScanParams();
params.match(key);
//扫描返回一百行,这里可以根据业务需求进行修改
params.count(1000);
String cursor = "0";
ScanResult scanResult = jedis.scan(cursor, params);
//scan.getStringCursor() 存在 且不是 0 的时候,一直移动游标获取
while (null != scanResult.getCursor()) {
//封装扫描的结果
set.addAll(scanResult.getResult());
if (!"0".equals(scanResult.getCursor())) {
scanResult = jedis.scan(scanResult.getCursor(), params);
} else {
break;
}
}
return set;
}
如上几句代码修改,搞了一下午,特此记录,以备查看!
问题原因与思路
由于
Map<String, JedisPool> clusterNodes = jedisClusterConnection.getNativeConnection().getClusterNodes();
被修改为:
Map<String, ConnectionPool> clusterNodes = ((JedisCluster) jedisClusterConnection.getNativeConnection()).getClusterNodes();
而 ConnectionPool 类中包裹的 Connection类 没有JedisPool类中包裹的 Jedis类的 info()方法和 scan()方法;
所以我们可以有两个解题思路:
1.使用Connection中的sendCommand 模拟 info()方法和 scan()方法(只是猜想、未实现);
2.如上文中自己构建 Jedis对象;