从keys到SCAN:Redis批量删除的进化之路

标签:Redis、批量删除、前缀匹配、性能优化


一、痛点分析:为什么需要批量删除指定前缀的键?

在 Redis 使用过程中,我们经常会遇到这样的场景:

  • 需要对某一类数据进行清理,例如用户会话、缓存数据等,而这些数据通常以某种前缀命名(如 user:session:*cache:data:*)。
  • 如果直接通过 redisTemplate.delete(key) 删除单个键,需要手动指定所有键,效率极低且不可维护。
  • 如果使用 redisTemplate.keys(prefix + "*") 查找并删除,虽然简单,但在键数量庞大的情况下(如百万级),会导致 Redis 服务阻塞,甚至引发宕机风险。

因此,我们需要一种高效、安全的方法,能够根据指定前缀批量删除键,同时避免性能问题。


二、解决方案:三种方法详解

以下是三种常见的实现方式,分别基于 keysSCAN 和 Lua 脚本。


方法 1:使用 keys 查找并删除

实现代码

// 获取 RedisTemplate 实例
RedisTemplate<String, Object> redisTemplate = ...;

// 定义前缀
String prefix = "CAR_TERMINAL_LOCATION:"; // 示例前缀

// 查找所有匹配的键
Set<String> keys = redisTemplate.keys(prefix + "*");

// 批量删除
if (keys != null && !keys.isEmpty()) {
    redisTemplate.delete(keys);
}

 原理

  • 通过 redisTemplate.keys(prefix + "*") 查找所有匹配的键。
  • 使用 redisTemplate.delete(keys) 批量删除。

优点

  1. 实现简单,代码清晰。
  2. 适合键数量较少的场景。

缺点

  1. keys 是阻塞操作,会一次性加载所有匹配的键到内存中。
  2. 当键数量庞大时,可能导致 Redis 服务阻塞,甚至宕机。
  3. 不适合生产环境的高并发场景。

方法 2:使用 SCAN 增量式删除

实现代码

// 获取 RedisTemplate 实例
RedisTemplate<String, Object> redisTemplate = ...;

// 定义前缀
String prefix = "CAR_TERMINAL_LOCATION:"; // 示例前缀

// 使用 RedisConnection 执行 SCAN
redisTemplate.execute((RedisCallback<Void>) connection -> {
    Cursor<byte[]> cursor = connection.scan(
        ScanOptions.scanOptions().match(prefix + "*").count(1000).build()
    );
    while (cursor.hasNext()) {
        connection.del(cursor.next());
    }
    return null;
});

原理

  • 通过 SCAN 命令增量式遍历匹配的键,避免一次性加载所有键到内存中。
  • 使用 connection.del() 逐个删除键。

优点

  1. 非阻塞操作,适合处理大量键的场景。
  2. 不会导致 Redis 服务阻塞,性能更优。
  3. 适合生产环境的高并发场景。

缺点

  1. 实现相对复杂,需要手动管理游标。
  2. 删除操作是逐个执行的,可能比批量删除稍慢。

方法 3:使用 Lua 脚本

实现代码

// Lua 脚本:删除所有匹配指定前缀的键
String luaScript = 
    "local keys = redis.call('KEYS', ARGV[1]) " +
    "for i, key in ipairs(keys) do " +
    "    redis.call('DEL', key) " +
    "end " +
    "return #keys";

// 定义前缀
String prefix = "CAR_TERMINAL_LOCATION:*";

// 执行 Lua 脚本
Long deletedCount = redisTemplate.execute(
    (RedisCallback<Long>) connection -> 
        connection.eval(
            luaScript.getBytes(), 
            ReturnType.INTEGER, 
            0, 
            prefix.getBytes()
        )
);

System.out.println("Deleted keys: " + deletedCount);

原理

  • 将删除逻辑封装到 Lua 脚本中,在 Redis 服务器端执行。
  • 通过 KEYS 查找所有匹配的键,并逐个删除。

优点

  1. 脚本在 Redis 服务器端执行,减少了网络开销。
  2. 适合需要原子性操作的场景。

缺点

  1. KEYS 命令是阻塞的,可能导致性能问题。
  2. Lua 脚本实现复杂,调试困难。
  3. 不推荐在键数量庞大的场景中使用。

三、方法对比:优缺点总结

方法优点缺点适用场景
keys1. 实现简单
2. 适合键数量较少的场景
1. 阻塞操作,可能导致 Redis 服务阻塞
2. 不适合键数量庞大的场景
小规模数据清理、开发测试环境
SCAN1. 非阻塞操作,性能更优
2. 适合处理大量键的场景
1. 实现复杂,需要手动管理游标
2. 删除操作逐个执行,可能稍慢
生产环境、高并发场景
Lua 脚本1. 脚本在 Redis 服务器端执行,减少网络开销
2. 支持原子性操作
1. KEYS 命令阻塞,可能导致性能问题
2. Lua 脚本实现复杂,调试困难
需要原子性操作的场景(慎用)

四、总结与建议
  1. 优先选择 SCAN
    • 在生产环境中,SCAN 是最推荐的方式,因为它非阻塞、性能更优,适合处理大量键的场景。
  2. 谨慎使用 keys
    • keys 适合键数量较少的场景,如开发测试环境,但不建议在生产环境中使用。
  3. Lua 脚本需谨慎
    • Lua 脚本适合需要原子性操作的场景,但由于 KEYS 命令的阻塞性,不建议在键数量庞大的场景中使用。
  4. 优化建议
    • 如果可能,尽量在设计 Redis 键时避免使用全局前缀匹配,而是通过其他方式(如哈希表)管理数据,减少删除操作的复杂性。

希望本文能帮助你更好地理解 Redis 批量删除指定前缀键的实现方法,避免性能问题!
欢迎在评论区留言讨论,点赞收藏支持! 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

凯哥Java

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值