redis 删除指定前缀的key(建议收藏)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
前言
在使用 Redis 时,我们经常需要对存储的键(Key)进行管理,尤其是当系统中存在大量以特定前缀命名的键时,如何高效、安全地删除这些键成为了一个关键问题。例如,在缓存系统中,可能需要定期清理以"cache:product:"为前缀的过期商品数据;在会话管理中,可能需要删除以"user:session:"开头的失效会话信息。
本文将从 Redis 的核心机制出发,结合实际场景,深入讲解如何删除指定前缀的 Key,并提供代码示例和优化建议。通过循序渐进的方式,帮助读者掌握这一实用技能,同时避免常见的性能陷阱。
Redis Key 的命名与管理
Key 的命名规范与作用
Redis 的 Key 是字符串类型,可以包含任意字符,但合理的命名规范能显著提升系统的可维护性。例如:
- 命名空间分隔:使用冒号(:)或下划线(_)分隔层级,如 "user:id:1001" 或 "logs:error:2023"。
- 前缀标识:通过前缀标识业务场景,如 "cache:" 表示缓存,"lock:" 表示分布式锁。
这种命名方式类似于图书馆的图书分类系统——通过前缀快速定位到某一类数据,便于批量操作。
Redis 删除 Key 的基础命令
Redis 提供了多个删除 Key 的命令,但直接删除指定前缀的 Key 并非其内置功能。以下是常用命令:
| 命令 | 描述 | 时间复杂度 |
|--------------|-------------------------------|------------|
| DEL key
| 删除单个 Key | O(1) |
| KEYS pattern
| 根据模式匹配 Key(不推荐生产环境使用) | O(N) |
注意:KEYS
命令在生产环境中可能因遍历全量数据导致阻塞,需谨慎使用。
方法一:结合 SCAN
和 DEL
命令
SCAN
命令的原理与优势
SCAN
是 Redis 提供的非阻塞遍历 Key 的命令,通过分页方式逐步扫描数据库。其核心参数包括:
- 游标(cursor):用于记录扫描进度,初始值为 0。
- 匹配模式(match):支持通配符
*
,例如"cache:*"
匹配所有以 "cache:" 开头的 Key。
类比:SCAN
相当于在图书馆中逐层翻阅书架,每次只查看一部分书籍,避免一次性搬空所有书架导致“图书馆瘫痪”。
具体实现步骤
以下是一个 Python 示例,使用 redis-py
库实现删除指定前缀的 Key:
import redis
def delete_keys_by_prefix(prefix, redis_client):
cursor = '0'
while cursor != 0:
cursor, keys = redis_client.scan(cursor=cursor, match=f"{prefix}*", count=1000)
if keys:
redis_client.delete(*keys)
print(f"Deleted {len(keys)} keys")
client = redis.Redis(host='localhost', port=6379, db=0)
delete_keys_by_prefix("cache:", client)
关键点解析:
- 分页扫描:通过
count
参数控制每次扫描的 Key 数量(如 1000),避免内存压力过大。 - 游标循环:循环执行
SCAN
直到游标返回 0,表示扫描完成。 - 批量删除:使用
DEL
命令批量删除匹配的 Key,减少网络开销。
方法二:使用 Lua 脚本实现原子性删除
原子操作的重要性
在高并发场景下,直接通过 SCAN
+ DEL
可能因网络延迟或中断导致部分 Key 未被删除。此时,可以使用 Lua 脚本将扫描和删除操作封装为原子操作。
类比:原子操作如同在银行取款时,先冻结账户余额再扣款,避免中途被其他操作干扰。
Lua 脚本实现
以下是一个 Lua 脚本示例,通过 redis.call
实现原子删除:
local cursor = "0"
repeat
cursor, keys = redis.call("SCAN", cursor, "MATCH", ARGV[1], "COUNT", 1000)
if #keys > 0 then
redis.call("DEL", unpack(keys))
end
until tonumber(cursor) == 0
调用方式(Python 示例):
script = """
...(上述 Lua 代码)
"""
client.eval(script, 0, "cache:*") # 参数1: 键数,参数2: 匹配模式
优势:
- 原子性:整个过程在 Redis 服务端执行,避免网络延迟影响。
- 性能优化:减少客户端与服务端的通信次数。
方法三:结合 KEYS
命令的快速方案(慎用)
KEYS
的适用场景与风险
在测试环境或 Key 数量极小的场景中,可以直接使用 KEYS
命令配合 DEL
:
keys_to_delete = client.keys("cache:*")
if keys_to_delete:
client.delete(*keys_to_delete)
风险提示:
- 阻塞问题:当 Key 数量超过万级时,
KEYS
会阻塞 Redis 线程,导致服务不可用。 - 内存消耗:一次性加载大量 Key 到内存可能触发 OOM(内存溢出)。
类比:这就像在繁忙的十字路口一次性暂停所有车辆,虽然能快速清空道路,但会导致整个交通系统瘫痪。
实际案例:清理过期缓存
场景描述
假设我们有一个电商系统,商品详情缓存的 Key 格式为 "product:detail:{id}"
,需要每天凌晨删除过期缓存。
实现方案
使用 SCAN
+ DEL
的方式编写定时任务:
from redis import StrictRedis
import time
def clean_product_cache():
r = StrictRedis(host='127.0.0.1', port=6379, db=0)
cursor = '0'
while True:
cursor, keys = r.scan(cursor=cursor, match='product:detail:*', count=1000)
if keys:
r.delete(*keys)
if cursor == '0':
break
time.sleep(0.01) # 避免 CPU 过度占用
if __name__ == "__main__":
clean_product_cache()
优化点:
- 分批次处理:通过
sleep
控制扫描频率,避免占用过多 CPU 资源。 - 灵活扩展:可调整
count
参数适应不同数据量。
注意事项与最佳实践
1. 测试环境验证
在生产环境执行删除操作前,务必在测试环境中验证脚本的正确性。例如:
def test_delete(prefix, redis_client):
cursor = '0'
while cursor != '0':
cursor, keys = redis_client.scan(cursor, match=f"{prefix}*", count=1000)
print(f"Found {len(keys)} keys: {keys}")
2. 监控与性能调优
- 监控扫描进度:通过日志或指标(如每秒删除 Key 数量)监控任务状态。
- 调整
COUNT
参数:根据服务器性能,合理设置SCAN
的COUNT
值。
3. 命名规范的长期价值
- 避免前缀冲突:确保不同业务模块的 Key 前缀唯一。
- 版本控制:如
"v1:users:*"
,便于未来迁移或清理旧版本数据。
结论
删除 Redis 中指定前缀的 Key 是一个需要综合考虑性能、安全性和可维护性的任务。通过 SCAN
+ DEL
的组合、Lua 脚本的原子操作,以及合理规避 KEYS
的风险,开发者可以高效完成这一操作。
在实际应用中,建议:
- 优先使用非阻塞的
SCAN
命令; - 在高并发场景中采用 Lua 脚本保证原子性;
- 通过命名规范减少误删风险。
掌握这些方法后,读者可以进一步探索 Redis 的高级功能(如过期策略、集群管理),持续提升分布式系统的优化能力。