redis 批量删除key(长文解析)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
在 Redis 开发中,批量删除 key 是一个高频操作场景。无论是清理过期数据、优化内存占用,还是应对突发业务需求(如促销后清理临时缓存),开发者都需要高效、安全地执行这一操作。然而,对于编程初学者和中级开发者而言,如何正确使用 Redis 的批量删除功能,避免误删数据或引发性能问题,常常是一大挑战。本文将从基础概念、方法对比、实战案例和注意事项四个维度,系统性地讲解这一主题,帮助读者掌握 Redis 批量删除 key 的核心技巧。
一、Redis 的 key 空间管理:为什么需要批量删除?
1.1 Redis 的内存模型
Redis 是一个基于内存的键值数据库,所有数据都存储在内存中。每个 key 都占用一定的内存空间,且 Redis 的 key 空间管理依赖于哈希表(Hash Table)结构。当 key 数量过多时,哈希表的扩容和 rehash 操作可能引发性能波动,因此及时清理无用 key 是优化 Redis 性能的关键。
1.2 批量删除的必要性
单条删除命令(如 DEL key
)在少量数据场景下足够高效,但当需要删除成千上万甚至百万级 key 时,逐条执行会带来以下问题:
- 高延迟:大量独立命令会占用网络带宽和 Redis 服务端的 CPU 时间片。
- 内存碎片:频繁的小批量删除可能引发内存碎片化,降低内存利用率。
- 业务风险:手动逐条删除容易误操作,导致关键数据被意外删除。
因此,掌握批量删除方法对开发者来说至关重要。
二、Redis 批量删除 key 的核心方法
2.1 方法一:SCAN + DEL 的组合模式
2.1.1 SCAN 命令的工作原理
Redis 的 SCAN
命令是非阻塞式迭代器,它通过游标(cursor)逐步遍历 key 空间,避免一次性加载所有 key 导致内存溢出。其核心特点包括:
- 渐进式:每次返回部分 key,开发者可控制每次迭代的步长(通过
COUNT
参数)。 - 无锁:不会阻塞其他命令执行,适合生产环境。
示例代码(Python):
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
cursor = 0
pattern = "user:*" # 要匹配的 key 模式,例如删除所有以 user: 开头的 key
while True:
cursor, keys = r.scan(cursor=cursor, match=pattern, count=1000)
if keys:
r.delete(*keys) # 批量删除当前批次的 key
if cursor == 0:
break
2.1.2 为什么选择 SCAN?
- 安全性:分批次处理,避免内存溢出或阻塞服务。
- 灵活性:支持通过
MATCH
参数匹配正则表达式(如user:2023-*
)。
2.2 方法二:使用 Lua 脚本实现原子性批量删除
当需要确保删除操作的原子性(即要么全部成功,要么全部失败)时,可以通过 Redis 的 Lua 脚本实现。
2.2.1 Lua 脚本的实现逻辑
-- 示例脚本:根据模式删除 key,并返回删除数量
local cursor = "0"
local keys_to_delete = {}
repeat
cursor, reply = redis.call("SCAN", cursor, "MATCH", ARGV[1], "COUNT", 1000)
for _, key in ipairs(reply) do
table.insert(keys_to_delete, key)
end
until tonumber(cursor) == 0
redis.call("DEL", unpack(keys_to_delete))
return #keys_to_delete
调用方式(Java 示例):
Jedis jedis = new Jedis("localhost");
String script = ...; // 上述 Lua 脚本内容
List<String> keys = jedis.evalsha(script, 1, "user:*");
2.2.2 Lua 脚本的优势
- 原子性:整个脚本在服务器单线程执行,避免中间状态暴露。
- 性能优化:减少客户端与服务端的往返次数。
2.3 方法三:UNLINK 命令的异步删除特性
Redis 3.0 引入的 UNLINK
命令与 DEL
类似,但它的删除操作是异步的:
DEL
:立即从内存中删除 key,并释放内存。UNLINK
:将 key 标记为可删除,实际内存释放由后台线程异步完成。
适用场景:
当需要快速返回删除结果,且对内存释放的实时性要求不高时(例如删除大量 key 后需立即返回 HTTP 响应)。
示例命令:
redis> UNLINK log:*
三、实战案例:电商大促后的缓存清理
3.1 场景描述
某电商平台在双十一大促期间,为每个用户生成临时优惠券 key(格式为 coupon:user:<id>:20231111
)。活动结束后,需清理所有与 20231111 相关的 key。
3.2 解决方案设计
- 使用 SCAN 迭代匹配 key:
redis> SCAN 0 MATCH coupon:user:*:20231111 COUNT 1000
- 分批次删除:将每批次的 key 存入列表,再通过管道(pipeline)批量执行
DEL
。
Python 实现代码:
pipe = r.pipeline(transaction=False)
while True:
cursor, keys = r.scan(cursor=cursor, match="coupon:user:*:20231111", count=1000)
if keys:
for key in keys:
pipe.delete(key)
pipe.execute()
if cursor == 0:
break
3.3 性能优化点
- 管道技术:通过
pipeline
减少网络往返。 - 非事务模式:设置
transaction=False
,避免分布式锁竞争。
四、注意事项与最佳实践
4.1 谨慎操作:备份与回滚
在生产环境中,执行批量删除前应:
- 备份数据:使用
SAVE
或BGSAVE
命令生成 RDB 文件。 - 测试环境预演:在测试集群中模拟操作,验证脚本逻辑。
4.2 性能监控与调优
- 监控内存使用:删除大量 key 后,通过
INFO memory
检查内存回收情况。 - 调整 SCAN 步长:根据服务器负载,动态调整
COUNT
参数(如从 1000 调整为 5000)。
4.3 版本兼容性
- Redis 2.8 以上支持
SCAN
和UNLINK
。 - Lua 脚本需注意语法兼容性,避免因版本差异导致执行失败。
结论
Redis 批量删除 key 是开发者需要掌握的核心技能之一。通过结合 SCAN
命令的渐进式遍历、Lua 脚本的原子性保障以及 UNLINK
的异步特性,开发者可以灵活应对不同场景下的数据清理需求。在实际操作中,务必遵循“备份先行、分批执行、监控优化”的原则,确保操作安全高效。随着 Redis 版本的迭代,建议持续关注新特性(如 Redis 7.0 的 FT.DROPINDEX
等批量操作命令),以提升开发效率。
掌握这些方法后,开发者不仅能解决当前问题,更能为构建高可用、高性能的 Redis 应用打下坚实基础。