在使用redis做数据的缓存时,有时还需要及时更新缓存。如果需要更新的key很多,没法直接获取,但是他们是符合一定正则表达式的,redis中也提供了 keys pattern 方法来为我们查询所有匹配 pattern 的keys,但是如果Redis的key太多的话,直接使用keys扫描会引起阻塞,这种情况再生产环境是非常危险的,所以需要进行分批扫描,再删除所有拿到的keys
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
public class RedisKeyCleaner {
private final RedisTemplate<String, Object> redisTemplate;
public RedisKeyCleaner(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 批量删除以指定前缀开头的键
public long deleteKeysByPrefix(String prefix) {
Set<String> keysToDelete = scanKeysByPrefix(prefix);
if (!keysToDelete.isEmpty()) {
return redisTemplate.delete(keysToDelete); // 批量删除,返回删除数量
}
return 0;
}
// 使用 SCAN 安全获取匹配的键
private Set<String> scanKeysByPrefix(String prefix) {
Set<String> keys = new HashSet<>();
// 构建扫描选项:匹配模式 + 每批数量
ScanOptions options = ScanOptions.scanOptions()
.match(prefix + "*") // 匹配前缀,如 "temp:*"
.count(1000) // 每批扫描数量(建议500~5000)
.build();
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
try (Cursor<byte[]> cursor = connection.scan(options)) {
while (cursor.hasNext()) {
// 将字节数组转为字符串键名
String key = new String(cursor.next(), StandardCharsets.UTF_8);
keys.add(key);
}
} catch (Exception e) {
throw new RuntimeException("SCAN 遍历异常", e);
}
return keys;
}
}