理解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,因此FLUSHDBFLUSHALL在那里需要格外小心,因为通常的“选定数据库”心智模型在这种情况下并不适用。

如果你的应用程序在独立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

UNLINKDEL具有相同的形状:

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命令的ASYNCSYNC修饰符。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:*的键,未观察到延迟增加”是那种以后能节省时间的说明。