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

发布时间:2026/9/29 4:14:13
Redis 前缀扫描删除实战:用 TaoToken 统一 Key 打通脚本配置与验证 1. 从一次线上缓存清理说起为什么 SCANDEL 总在环境之间翻车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 连接与前缀规则再用一个游标安全的 SCANDEL 脚本完成批量删除最后给出验证删除结果的命令与预期输出。适合正在做缓存治理、临时数据清理、多环境配置收敛的后端和运维同学。核心检索词就是 redis 前缀扫描删除、SCAN 游标、批量 DEL、多环境 Key 管理。下面从配置骨架开始一步步把脚本跑通。2. 前置准备用 TaoToken 统一管理 Key 与多环境配置在写删除脚本之前先把「连哪个 Redis、用哪个前缀、批量多大」这三件事从代码里抽出来。我的做法是引入 TaoToken 作为统一的模型与配置入口把多环境的连接信息和清理规则集中到一份config.toml里脚本只读配置、不硬编码。TaoToken 的官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_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_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建完成后Key 只在创建时完整显示一次记得立刻复制保存。如果你后续想让脚本在删除前用模型判断「这个前缀是否属于可清理范围」可以走模型对话接口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果只是纯 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(pathconfig.toml, envdev): 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( hostenv_cfg[redis_host], portenv_cfg[redis_port], dbenv_cfg[redis_db], passwordenv_cfg[redis_pass] or None, decode_responsesFalse, ) 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( cursorcursor, matchmatch, countscan_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(fprefix{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(envenv) 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_responsesFalse是为了避免二进制 key 被错误解码删除时按原始字节匹配更安全。3.3 如果你更习惯 Java 客户端原 excerpt 里用的是RedisTemplate加Cursor的方式思路一致但要注意ScanOptions.count不要设成Integer.MAX_VALUE。改成固定值比如 500并且把删除逻辑抽成独立方法避免在forEachRemaining里直接操作集合导致并发修改。ScanOptions options ScanOptions.scanOptions() .match(DeviceImport:*) .count(500) .build(); SetString buffer new HashSet(); try (Cursorbyte[] 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_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。把前缀和业务背景贴进去让模型判断是否误伤比人工翻代码快。提示验证阶段建议先用--scan而不是KEYSKEYS在大库上会阻塞验证本身也可能引发问题。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_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。需要管理多个环境的 Key 时API Keys 页面可以直接创建和轮换https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。接入细节和参数说明看文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你用 Claude Code 做脚本开发Anthropic 兼容入口在这里https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeanthropicutm_campaignrewrite 。最后留一个我踩过的坑第一次跑生产清理时我把delete_batch设成了 5000结果单条 DEL 命令耗时接近 2 秒触发了客户端超时重试反而重复删除。后来改成 200稳定多了。批量删除这件事宁可多跑几轮也不要单次贪多。