理解Redis键空间:删除与检查命令
通过这份全面指南,解锁Redis键空间管理的强大功能。学习如何安全地使用`SCAN`检查数据(以及为什么在生产环境中避免使用`KEYS`),并高效地使用`DEL`和非阻塞的`UNLINK`删除键。理解`FLUSHDB`和`FLUSHALL`的破坏性,并发现维护健康、高性能Redis实例的最佳实践。
理解Redis键空间:删除与检查命令
Redis键空间只是当前选定数据库中键的集合,但检查和删除这些键的方式决定了清理过程是平淡无奇,还是会导致应用程序在流量高峰时停滞。
大多数团队都是通过惨痛教训才明白这一点。有人需要从暂存缓存中删除session:*键,运行KEYS session:*,看到列表,然后尝试在生产环境中使用同样的习惯。在小型数据库上,这感觉没问题。但在拥有数百万个键的繁忙实例上,该命令可能会长时间占用服务器,导致无关请求在其后排队。Redis处理命令非常快,但遍历整个键空间的命令仍然需要完成工作。
对于日常操作,分两步思考:先安全地找到键,然后安全地删除它们。不要将键检查视为无害的读取操作。读取命令也可能代价高昂。
从你实际使用的数据库开始
在删除任何内容之前,确认端点和逻辑数据库。
redis-cli -h redis.example.internal -p 6379 INFO keyspace
输出如下:
# Keyspace
db0:keys=154233,expires=129900,avg_ttl=2851412
db2:keys=32,expires=32,avg_ttl=60000
这告诉你哪些逻辑数据库包含键。许多Redis部署只使用数据库0。Redis Cluster仅支持数据库0,因此FLUSHDB和FLUSHALL在那里需要格外小心,因为通常的“选定数据库”心智模型在这种情况下并不适用。
如果你的应用程序在独立Redis上使用编号数据库,请明确选择正确的数据库:
redis-cli -n 2 DBSIZE
DBSIZE返回选定数据库中的键数量。它不显示名称,也不能替代检查过程,但它是清理前后进行合理性检查的好方法。
仅在键空间小且可丢弃时使用KEYS
KEYS pattern返回所有匹配glob样式模式的键:
KEYS user:*
KEYS cache:product:???
KEYS *
模式规则很方便:*匹配任意序列,?匹配一个字符,而括号范围如[0-9]匹配范围内的一个字符。
问题不在于正确性。问题在于KEYS在一个命令中扫描整个键空间。在只有几百个键的本地开发Redis上,这很方便。在共享缓存上,当Redis忙于生成结果时,它可能会增加其他客户端的延迟。该命令被记录为具有线性复杂度的键空间命令,因此它不应成为正常生产清理脚本的一部分。
我仍然在两个地方使用KEYS:
- 编写测试时使用的可丢弃本地Redis。
- 小型暂存数据库,我已检查过
DBSIZE,知道该命令不会让我意外。
在其他所有地方,使用SCAN。
在生产检查中使用SCAN
SCAN基于游标:
SCAN 0 MATCH user:* COUNT 100
Redis返回两样东西:下一个游标和一批键。从游标0开始。使用返回的游标继续扫描。当Redis再次返回游标0时停止。
1) "24576"
2) 1) "user:100"
2) "user:101"
COUNT是一个提示,而不是承诺。Redis可能返回更多键、更少键,甚至某个迭代中没有键。MATCH过滤返回的内容,但Redis仍然会遍历键空间。窄模式有助于减少客户端工作,但它不会神奇地让每次扫描都免费。
对于Shell工作,更喜欢redis-cli --scan,因为它隐藏了游标循环:
redis-cli --scan --pattern 'session:*'
要计算匹配键的数量而不打印所有键:
redis-cli --scan --pattern 'session:*' | wc -l
要在删除前检查类型:
redis-cli --scan --pattern 'session:*' | head
redis-cli TYPE session:abc123
redis-cli TTL session:abc123
redis-cli MEMORY USAGE session:abc123
TTL对于清理决策特别有用。如果缓存命名空间已经有合理的过期时间,你可能根本不需要批量删除。让键自然过期通常比在业务高峰期间强制大规模删除风险更小。
使用DEL删除已知键
DEL删除一个或多个键,并返回存在的键数量:
DEL session:abc123
DEL session:abc123 session:def456 session:ghi789
对于小键,DEL通常没问题。删除字符串键或小哈希并不可怕。可怕的情况是删除大型聚合值,例如包含大量元素的列表、用作索引的集合,或增长远超原始用途的哈希。Redis从键空间中移除键,但释放大值可能会在主路径上花费时间。
如果你知道要删除的键很小,请使用DEL。如果你要删除许多键或不确定它们有多大,请使用UNLINK。
对于大型或批量删除,优先使用UNLINK
UNLINK与DEL具有相同的形状:
UNLINK session:abc123
UNLINK cache:old:1 cache:old:2 cache:old:3
重要的区别在于内存回收。UNLINK立即从键空间中移除键,然后异步释放内存。这使得它在清理可能包含大值的键时成为更安全的默认选择。
这并不意味着UNLINK是神奇的。如果你的脚本尽可能快地发现并取消链接数百万个键,你仍然可能造成压力。Redis仍然需要处理命令,副本仍然需要接收更改,内存仍然需要回收。限制批量清理的速度,以便在运行时观察延迟和内存。
一个实用的清理循环如下:
redis-cli --scan --pattern 'session:*' |
xargs -r -L 100 redis-cli UNLINK
这以每批100个键的方式删除。根据你的环境调整批处理大小。在安静的维护窗口,你可以使用更大的批次。在用户流量共享的热缓存上,较小的批次加上批次之间的短暂休眠可能更友好:
redis-cli --scan --pattern 'session:*' |
while read -r key; do
redis-cli UNLINK "$key" >/dev/null
sleep 0.005
done
这个版本较慢,但易于停止且易于理解。
小心模式删除
Redis故意不提供DEL user:*。你需要自己结合扫描和删除。这种摩擦是有用的,因为模式删除是事故高发区。
在删除之前:
redis-cli --scan --pattern 'user:*' | head -50
redis-cli --scan --pattern 'user:*' | wc -l
查看第一个样本。计算目标大小。如果你预期的清理是“几千个废弃会话”,而计数是“数据库的大部分”,请停止并修复模式。
使用让清理变得平淡的命名约定:
app:prod:session:<id>
app:prod:rate-limit:<user-id>
app:prod:cache:product:<id>
这比session:<id>更冗长,但它允许你精确地定位命名空间。在Redis Cluster中,键名可能还包含哈希标签,如cart:{user123}:items,用于槽位放置。在编写宽泛模式之前,请注意这些约定。
FLUSHDB和FLUSHALL是重置按钮
FLUSHDB从选定数据库中移除所有键:
FLUSHDB
FLUSHDB ASYNC
FLUSHALL从所有逻辑数据库中移除所有键:
FLUSHALL
FLUSHALL ASYNC
现代Redis支持flush命令的ASYNC和SYNC修饰符。ASYNC在后台安排释放,这有助于避免大型同步内存释放暂停。它不会使操作可逆。一旦键从键空间中消失,你的应用程序就会看到它们已消失。
在使用任一命令之前,我希望进行三项检查:
redis-cli ROLE
redis-cli INFO keyspace
redis-cli CONFIG GET dir
ROLE有助于确认你连接的是主节点还是副本。INFO keyspace显示将受到影响的键。CONFIG GET dir通常提供关于你所在实例的另一个提示,因为数据目录往往包含特定于环境的路径。
对于开发重置脚本,要明确:
redis-cli -h 127.0.0.1 -p 6379 -n 0 FLUSHDB ASYNC
避免使用默认值运行redis-cli FLUSHALL的脚本。当脚本在另一台主机、另一个容器或具有不同环境变量的CI运行器上运行时,默认值会发生变化。
删除后验证
清理后,检查计数和应用程序行为:
redis-cli --scan --pattern 'session:*' | wc -l
redis-cli INFO memory
redis-cli INFO stats | grep expired_keys
在UNLINK或异步flush之后,内存可能不会立即下降,因为释放是在后台进行的,并且分配器可能会保留内存以供重用。这并不自动意味着内存泄漏。观察used_memory、延迟以及键计数是否朝你期望的方向移动。
对于生产更改,在运行之前写下确切的命令和模式。安全的Redis清理不仅仅是正确的命令。它是在正确的实例上,使用你已经采样过的模式,在你可以观察结果的窗口期内执行正确的命令。
更安全的生产清理运行手册
对于真正的清理,我喜欢将命令变成一个小型运行手册,而不是从记忆中键入的一行命令。运行手册不需要花哨。它应该回答四个问题:哪个实例、什么模式、多少键、以及多快。
从只读检查开始:
redis-cli -h redis.example.internal -p 6379 ROLE
redis-cli -h redis.example.internal -p 6379 INFO keyspace
redis-cli -h redis.example.internal -p 6379 --scan --pattern 'app:prod:session:*' | head -20
redis-cli -h redis.example.internal -p 6379 --scan --pattern 'app:prod:session:*' | wc -l
然后检查几个代表性键:
redis-cli TYPE app:prod:session:sample
redis-cli TTL app:prod:session:sample
redis-cli MEMORY USAGE app:prod:session:sample
如果样本显示键应该在几分钟内自然过期,请等待而不是删除。如果键没有TTL且明显属于废弃数据,则继续进行限速清理。保持一个终端打开以观察延迟或内存:
redis-cli --latency
redis-cli INFO memory
对于共享生产Redis,我更喜欢一个可以在不丢失意图的情况下停止的脚本:
redis-cli --scan --pattern 'app:prod:session:*' |
while read -r key; do
redis-cli UNLINK "$key" >/dev/null
sleep 0.002
done
这不是最快的版本。它是让你有机会注意到延迟是否变化、客户端是否抱怨或模式是否比预期更宽的版本。当实例安静且计数适中时,使用xargs -L 100进行批处理是可以的。关键在于有意选择速度。
另一个有用的习惯:在事件工单或部署说明中保存前后的计数。“已删除会话键”是不够的。“使用UNLINK从redis-cache-01的db0中删除了48,213个匹配app:prod:session:*的键,未观察到延迟增加”是那种以后能节省时间的说明。