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();
        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对象;

### Spring Boot 3Redis集成使用Jedis客户端 #### 集成概述 为了使Spring Boot应用程序能够连接并操作Redis数据库,通常会引入特定的库来简化这一过程。对于采用Jedis作为底层通信工具的情况,在Spring Boot项目中集成就显得尤为重要[^1]。 #### 添加依赖项 在`pom.xml`文件内加入如下Maven依赖声明: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 如果需要指定版本 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <!-- 版本号应根据实际情况调整 --> </dependency> ``` 上述配置确保了应用具备访问Redis所需的基础组件以及Jedis驱动程序的支持能力[^2]。 #### 自定义配置类 创建名为`RedisConfig.java`的新Java类用于定制化设置: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @Configuration public class RedisConfig { @Bean public JedisPool jedisPool() { final String host = "localhost"; // 替换为实际服务器地址 int port = 6379; // 默认端口 JedisPoolConfig poolConfig = new JedisPoolConfig(); return new JedisPool(poolConfig, host, port); } } ``` 这段代码片段展示了如何通过编程方式构建一个可重用的Jedis连接池实例,并将其注册给Spring容器管理[^3]。 #### 应用属性配置 编辑位于资源路径下的`application.properties`或`application.yml`文件,添加必要的参数以便更好地控制Redis行为: ```properties # application.properties 示例 spring.redis.host=localhost spring.redis.port=6379 ``` 或者如果是YAML格式,则看起来像这样: ```yaml # application.yml 示例 spring: redis: host: localhost port: 6379 ``` 这些设定允许开发者轻松更改目标Redis服务的位置和其他选项而无需修改任何源代码逻辑[^4]。 #### 测试连接功能 最后一步是在控制器或其他业务层组件里验证能否成功建立至Redis的数据链路。这里给出一段简单的测试方法实现: ```java @RestController @RequestMapping("/api/redis") public class RedisController { private static final Logger logger = LoggerFactory.getLogger(RedisController.class); @Autowired private JedisPool jedisPool; @GetMapping("/testConnection") public ResponseEntity<String> testConnection(){ try (Jedis jedis = jedisPool.getResource()){ String pongResponse = jedis.ping(); if ("PONG".equals(pongResponse)){ return ResponseEntity.ok("Connected to Redis successfully!"); }else{ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to connect."); } } catch(Exception e){ logger.error(e.getMessage(),e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while connecting."); } } } ``` 此接口提供了一个便捷的方式去确认当前环境是否已经正确设置了通往Redis的服务通道[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窦再兴

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值