Java 使用 lettuce-core 批量扫描与删除 Redis 键:单节点与集群实战
在 Java 生态中,Redis 客户端的选择至关重要。lettuce-core 是一款高性能、基于 Netty 的非阻塞 Redis 客户端,目前已被 Spring Boot 作为默认的 Redis 客户端。相比于传统的 Jedis,lettuce-core 采用了更底层的 Netty 框架,原生支持异步、响应式编程模型,并且在连接池管理、线程安全性以及集群拓扑自动刷新方面表现更为优异。对于需要高并发处理或复杂集群运维的场景,lettuce-core 无疑是更现代、更可靠的选择。
本文将结合实际业务场景,演示如何使用 lettuce-core 在单节点和集群模式下,安全地批量扫描并删除指定模式的键。
引入 lettuce-core 依赖
首先,我们需要在项目中引入 lettuce-core 依赖。以下是当前推荐的稳定版本配置:
<!-- lettuce-core 官方依赖,提供高性能的 Redis 客户端支持 -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.5.5.RELEASE</version>
</dependency>
集群模式:批量扫描与删除
在 Redis 集群模式下,键分布在不同的节点上,因此需要使用 RedisClusterClient 和 RedisAdvancedClusterCommands 来执行跨节点的扫描操作。
以下代码展示了如何安全地遍历并删除匹配 sit_eds:* 模式的键。这里使用了 SCAN 命令而非 KEYS 命令,以避免在生产环境中阻塞 Redis 服务。
@Test
public void clearAllSIT() {
// 配置 Redis 认证信息,使用占位符替代真实密码
RedisCredentialsProvider credentialsProvider = new StaticCredentialsProvider("default", "YOUR_PASSWORD".toCharArray());
RedisURI redisURI = RedisURI.create("redis://10.100.242.186:6379");
redisURI.setCredentialsProvider(credentialsProvider);
// 创建集群客户端并建立连接
RedisClusterClient clusterClient = RedisClusterClient.create(redisURI);
try (StatefulRedisClusterConnection<String, String> connect = clusterClient.connect()) {
RedisAdvancedClusterCommands<String, String> sync = connect.sync();
// 配置扫描参数:匹配模式与单次迭代数量限制
ScanArgs scanArgs = ScanArgs.Builder
.matches("sit_eds:*")
.limit(100); // 注意:limit 控制的是每次迭代返回的键数量,而非总数
// 初始化游标,开始循环扫描
ScanCursor scanCursor = ScanCursor.INITIAL;
int count = 0;
do {
// 执行扫描,获取当前批次的键
KeyScanCursor<String> keyScanCursor = sync.scan(scanCursor, scanArgs);
List<String> keys = keyScanCursor.getKeys();
for (String key : keys) {
log.info("found key: {}", key);
count++;
sync.del(key);// 删除key
}
// 更新游标,用于下一轮迭代
scanCursor = keyScanCursor;
} while (!scanCursor.isFinished());
log.info("扫描完成,匹配 sit_eds:* 的key总数:{}", count);
} finally {
// 确保客户端资源被正确释放
clusterClient.shutdown();
}
}
技术解析:SCAN 游标机制
在上述代码中,SCAN 命令的核心在于游标(Cursor)机制。与 KEYS * 一次性返回所有结果不同,SCAN 采用增量式迭代:
- 服务端维护一个游标状态,每次调用
SCAN只返回一小部分匹配的键和下一个游标值。 - 客户端拿着新的游标继续请求,直到服务端返回的游标值为
0(即isFinished()为 true),表示遍历完成。 - 这种机制将一次大操作拆分为多次小操作,极大降低了对 Redis 主线程的阻塞风险,是生产环境批量处理键的唯一推荐方式。
单节点模式:适配与调整
如果你的 Redis 是单节点部署,代码逻辑基本一致,但需要替换客户端和连接类型。主要变更点如下:
RedisClusterClient替换为RedisClientStatefulRedisClusterConnection替换为StatefulRedisConnectionRedisAdvancedClusterCommands替换为RedisCommands
以下是单节点模式的完整实现:
@Test
public void clearAllSIT() {
// 配置认证信息,密码请使用占位符
RedisCredentialsProvider credentialsProvider = new StaticCredentialsProvider("default", "YOUR_PASSWORD".toCharArray());
RedisURI redisURI = RedisURI.create("redis://10.100.242.186:6379");
redisURI.setCredentialsProvider(credentialsProvider);
// 创建单节点客户端
RedisClient redisClient = RedisClient.create(redisURI);
try (StatefulRedisConnection<String, String> connect = redisClient.connect()) {
RedisCommands<String, String> sync = connect.sync();
// 扫描参数配置
ScanArgs scanArgs = ScanArgs.Builder
.matches("sit_eds:*")
.limit(100); // 单次迭代数量限制
// 游标迭代逻辑
ScanCursor scanCursor = ScanCursor.INITIAL;
int count = 0;
do {
KeyScanCursor<String> keyScanCursor = sync.scan(scanCursor, scanArgs);
List<String> keys = keyScanCursor.getKeys();
for (String key : keys) {
log.info("found key: {}", key);
count++;
sync.del(key);// 删除key
}
// 更新游标
scanCursor = keyScanCursor;
} while (!scanCursor.isFinished());
log.info("扫描完成,匹配 sit_eds:* 的key总数:{}", count);
} finally {
// 释放单节点客户端资源
redisClient.shutdown();
}
}
总结与注意事项
使用 lettuce-core 进行 Redis 键的批量管理时,请务必注意以下几点:
- 严禁在生产环境使用 KEYS 命令:
KEYS命令的时间复杂度为 O(N),在键数量庞大时会导致 Redis 瞬间无响应。始终使用SCAN系列命令。 - 合理设置 limit 参数:
ScanArgs.limit()并不是精确的返回数量,Redis 不保证每次严格返回该数量的键,但它能有效控制单次网络请求和 CPU 处理的开销。建议设置在 50-200 之间。 - 资源释放:
lettuce-core的连接和客户端是非阻塞的,但依然需要确保在finally块中调用shutdown()方法,防止连接泄漏。 - 敏感信息安全:在代码提交或分享时,务必将密码替换为环境变量或配置中心读取的方式,切勿硬编码真实密码。