news 2026/9/29 4:14:12

Redis 前缀扫描删除实战:用 TaoToken 统一 Key 打通脚本配置与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis 前缀扫描删除实战:用 TaoToken 统一 Key 打通脚本配置与验证

1. 从一次线上缓存清理说起:为什么 SCAN+DEL 总在环境之间翻车

Redis 里按前缀批量删 key,是运维和业务开发都绕不开的动作。比如设备导入产生的临时媒体 ID 缓存、活动期间写入的activity:2024:*标记、灰度环境遗留的gray:user:*数据,这些 key 往往没有统一 TTL,只能靠脚本主动清理。问题在于,很多团队第一次写清理脚本时,都是直接在业务代码里塞一段scan加delete,跑通一次就上线,结果换到测试环境、预发环境、生产环境时,连接地址、库号、前缀、批量大小全散落在不同文件里,改一个参数要翻三四个仓库。

更麻烦的是,KEYS *在生产环境是禁忌,SCAN的游标语义又容易被误用。我见过最典型的错误是:一边SCAN一边DEL,游标还没走完,key 空间已经变了,导致部分 key 被跳过;或者把COUNT设成Integer.MAX_VALUE,单次返回几十万 key,直接把客户端内存打满。这些坑在单机测试时看不出来,一到真实数据量就暴露。

这篇内容聚焦一个具体场景:用一份可复制的config.toml统一管理多环境 Redis 连接与前缀规则,再用一个游标安全的 SCAN+DEL 脚本完成批量删除,最后给出验证删除结果的命令与预期输出。适合正在做缓存治理、临时数据清理、多环境配置收敛的后端和运维同学。核心检索词就是 redis 前缀扫描删除、SCAN 游标、批量 DEL、多环境 Key 管理。下面从配置骨架开始,一步步把脚本跑通。

2. 前置准备:用 TaoToken 统一管理 Key 与多环境配置

在写删除脚本之前,先把「连哪个 Redis、用哪个前缀、批量多大」这三件事从代码里抽出来。我的做法是引入 TaoToken 作为统一的模型与配置入口,把多环境的连接信息和清理规则集中到一份config.toml里,脚本只读配置、不硬编码。TaoToken 的官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注册后在控制台生成 API Key 即可。

这里要区分两个概念:TaoToken 负责的是「配置与调用凭证的统一管理」,Redis 连接本身仍然走你自己的 Redis 实例。也就是说,config.toml里既放 Redis 的 host/port/db,也放 TaoToken 的 API Key(用于脚本里需要调用模型做前缀语义校验或日志归因的场景)。这样做的价值在于:多环境切换时只改一份配置文件,脚本逻辑完全复用。

你需要先拿到 TaoToken 的 API Key,进入控制台创建即可:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建完成后,Key 只在创建时完整显示一次,记得立刻复制保存。如果你后续想让脚本在删除前用模型判断「这个前缀是否属于可清理范围」,可以走模型对话接口:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。如果只是纯 Redis 清理,API Key 主要用于配置文件的统一鉴权字段,不强制调用。

注意:不要把生产 Redis 的密码和 TaoToken 的 Key 提交到 Git。config.toml建议加入.gitignore,仓库里只保留config.example.toml。

3. 可复制配置:config.toml 骨架与 SCAN 游标删除脚本

3.1 config.toml 骨架

下面这份配置把环境、Redis 连接、清理规则、TaoToken 凭证分开管理。你可以直接复制,按环境改[env.prod]这类段落。

# config.toml # 多环境 Redis 前缀清理配置骨架 [taotoken] api_base = "https://taotoken.net/api" api_key = "sk-你的TaoTokenKey" # 用于删除前的前缀语义校验,可选 model = "gpt-4o-mini" [scan] # 单次 SCAN 返回的 key 数量,建议 200-1000 count = 500 # 每积累多少 key 执行一次 DEL delete_batch = 200 # 游标最大循环次数,防止异常时死循环 max_loops = 100000 [env.dev] redis_host = "127.0.0.1" redis_port = 6379 redis_db = 0 redis_pass = "" prefix = "DeviceImport:*" [env.staging] redis_host = "10.0.1.21" redis_port = 6379 redis_db = 1 redis_pass = "staging_pass" prefix = "DeviceImport:*" [env.prod] redis_host = "10.0.2.31" redis_port = 6379 redis_db = 0 redis_pass = "prod_pass" prefix = "DeviceImport:*"

关键参数说明:count控制单次 SCAN 的提示数量,Redis 不保证精确返回这么多,但能避免一次拉太多;delete_batch控制累积多少 key 后批量 DEL,避免单次 DEL 参数过大;max_loops是安全阀,防止游标异常时脚本空转。

3.2 SCAN 游标删除脚本(Python 版)

下面这个脚本用redis-py实现,核心是「游标推进 + 批量累积 + 分批删除」,全程不阻塞主线程太久,也不会因为边扫边删而漏 key。

# redis_prefix_clean.py import tomllib import redis def load_config(path="config.toml", env="dev"): with open(path, "rb") as f: cfg = tomllib.load(f) return cfg, cfg["env"][env], cfg["scan"] def clean_by_prefix(cfg, env_cfg, scan_cfg): client = redis.Redis( host=env_cfg["redis_host"], port=env_cfg["redis_port"], db=env_cfg["redis_db"], password=env_cfg["redis_pass"] or None, decode_responses=False, ) prefix = env_cfg["prefix"] match = prefix if prefix.endswith("*") else prefix + "*" cursor = 0 loops = 0 total_deleted = 0 buffer = [] while True: cursor, keys = client.scan( cursor=cursor, match=match, count=scan_cfg["count"], ) if keys: buffer.extend(keys) # 累积到阈值就删 while len(buffer) >= scan_cfg["delete_batch"]: batch = buffer[:scan_cfg["delete_batch"]] total_deleted += client.delete(*batch) buffer = buffer[scan_cfg["delete_batch"]:] loops += 1 if cursor == 0: break if loops > scan_cfg["max_loops"]: raise RuntimeError("SCAN 循环次数超限,已中止") # 删除最后一批 if buffer: total_deleted += client.delete(*buffer) print(f"prefix={match} deleted={total_deleted} loops={loops}") return total_deleted if __name__ == "__main__": import sys env = sys.argv[1] if len(sys.argv) > 1 else "dev" cfg, env_cfg, scan_cfg = load_config(env=env) clean_by_prefix(cfg, env_cfg, scan_cfg)

运行方式:

python redis_prefix_clean.py dev python redis_prefix_clean.py staging python redis_prefix_clean.py prod

这里有个细节值得说:client.delete(*batch)在 key 数量很大时,参数展开会占用较多内存,所以delete_batch不要设太大,200 到 500 比较稳。另外decode_responses=False是为了避免二进制 key 被错误解码,删除时按原始字节匹配更安全。

3.3 如果你更习惯 Java 客户端

原 excerpt 里用的是RedisTemplate加Cursor的方式,思路一致,但要注意ScanOptions.count不要设成Integer.MAX_VALUE。改成固定值,比如 500,并且把删除逻辑抽成独立方法,避免在forEachRemaining里直接操作集合导致并发修改。

ScanOptions options = ScanOptions.scanOptions() .match("DeviceImport:*") .count(500) .build(); Set<String> buffer = new HashSet<>(); try (Cursor<byte[]> cursor = connection.scan(options)) { while (cursor.hasNext()) { buffer.add(new String(cursor.next(), StandardCharsets.UTF_8)); if (buffer.size() >= 200) { redisTemplate.delete(buffer); buffer.clear(); } } if (!buffer.isEmpty()) { redisTemplate.delete(buffer); } }

4. 验证请求与成功结果:确认 key 真的被删干净

删完之后不能只看脚本打印的deleted数量,还要独立验证。最直接的方式是用redis-cli再扫一遍,确认匹配结果为空。

redis-cli -h 10.0.2.31 -p 6379 -n 0 --scan --pattern "DeviceImport:*" | head -n 20

预期输出:命令执行后没有任何 key 打印出来,直接返回空。如果还有残留,说明前缀匹配规则或库号选错了。

再确认一下删除数量是否和脚本输出一致:

redis-cli -h 10.0.2.31 -p 6379 -n 0 DBSIZE

对比删除前后的DBSIZE,差值应该接近脚本报告的deleted数量。注意DBSIZE包含该库所有 key,如果期间有其他写入,差值会有偏差,所以最好在低峰期验证。

如果你在脚本里接了 TaoToken 做前缀语义校验,可以用模型对话接口快速确认「这个前缀是否属于可清理范围」:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。把前缀和业务背景贴进去,让模型判断是否误伤,比人工翻代码快。

提示:验证阶段建议先用--scan而不是KEYS,KEYS在大库上会阻塞,验证本身也可能引发问题。

5. 本篇常见错排查:SCAN 漏 key、DEL 报错、多环境串库

5.1 SCAN 边扫边删导致漏 key

这是最隐蔽的坑。SCAN的游标是基于哈希槽的,如果你在扫描过程中删除了大量 key,哈希表可能触发 rehash,游标推进时部分 key 会被跳过。解决办法就是本文脚本的做法:先累积到 buffer,再批量删除,而不是扫到一个删一个。批量删除虽然也会改变 key 空间,但影响范围可控,配合count参数能大幅降低漏 key 概率。

5.2 DEL 参数过多报错

client.delete(*batch)在batch过大时会触发参数数量限制,或者单次命令耗时过长。把delete_batch控制在 200 到 500,既能减少往返次数,又不会让单条命令过重。如果用的是集群模式,还要注意跨 slot 的 key 不能放在同一条 DEL 里,需要按 slot 分组。

5.3 多环境串库

config.toml里每个环境都有独立的redis_db和prefix,但脚本运行时如果环境参数传错,比如把prod写成dev,就会连到错误的库。建议在脚本启动时打印当前环境、host、db、prefix,让人一眼确认:

print(f"[env={env}] host={env_cfg['redis_host']} db={env_cfg['redis_db']} prefix={env_cfg['prefix']}")

5.4 前缀匹配写错

match参数是 glob 风格,DeviceImport:*能匹配DeviceImport:123,但匹配不到DeviceImport123。如果你的 key 命名是DeviceImport_123,就要写成DeviceImport_*。建议先用--scan --pattern在命令行确认匹配结果,再写进配置。

5.5 权限与连接问题

生产 Redis 通常有 ACL 或密码,redis_pass为空时会连接失败。另外部分云 Redis 禁用了SCAN或限制了DEL频率,遇到NOPERM或ERR unknown command时,先确认账号权限,再考虑改用分批UNLINK异步删除。

6. 把配置和脚本收进你的运维工具箱

整套流程跑下来,核心就三件事:一份config.toml管住多环境的连接与前缀,一个游标安全的脚本负责扫描和批量删除,一组redis-cli命令做独立验证。TaoToken 在这里的角色是统一凭证与配置入口,让脚本不散落硬编码。如果你后续要把这套清理逻辑接进 CI 或定时任务,建议走 Coding Plan 做长期编码与 Agent 编排:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。需要管理多个环境的 Key 时,API Keys 页面可以直接创建和轮换:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。接入细节和参数说明看文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。如果你用 Claude Code 做脚本开发,Anthropic 兼容入口在这里:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecodeanthropic&utm_campaign=rewrite 。

最后留一个我踩过的坑:第一次跑生产清理时,我把delete_batch设成了 5000,结果单条 DEL 命令耗时接近 2 秒,触发了客户端超时重试,反而重复删除。后来改成 200,稳定多了。批量删除这件事,宁可多跑几轮,也不要单次贪多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 4:13:38

5分钟把Claude Code搬进飞书:TaoToken统一Key接入cc-connect配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华