背景
几个月前,我查了一下 NAS 上摄像头录像占用的空间。结果有点出乎意料:
- 一块 8.2TB 的共享盘,已用 85%
- 其中摄像头录像占了约 3.6TB、3.1 万个文件
- 循环周期是 1 年——最早的数据大约 2025 年 7 月开始被覆盖
这意味着一个真实的问题:如何在摄像头覆盖旧数据之前,把所有有价值的录像处理完?
而且这不是一次性问题。如果现在处理完了,未来还要持续处理新进来的视频。等到追上实时,就能把循环周期从 1 年缩短到 6 个月,释放大约一半的空间。
三个目录,三种优先级
摄像头写入的目录结构分成三大块:
- 活跃目录 — 3.6TB,约 3.1 万个文件,摄像头持续写入。这是最紧急的一块,因为会被循环覆盖
- 归档目录 — 约 700GB,主要是去年的数据,暂时安全
- 旧存档 — 约 400GB,更早的数据,不着急处理
优先级很明确:活跃目录优先,因为数据真的会丢。归档和旧存档暂时不会消失。

核心问题:多路处理不重复
我背后跑着一套自动化视频处理流水线,对摄像头录像做后处理——截取关键片段、生成回放。最初是单路处理,后来需要扩展到多路并行。
几个关键需求:
- CI 构建和本地开发(workspace)共用同一套输入/输出池,不区分”是什么方式处理的”
- 支持多路 CI 同时运行
- 共用一个输入池时,保证同一个文件不会被两个处理器同时处理
这就需要一个分布式锁:多方(可能在不同的机器上)要就”这个文件归谁”达成共识。

方案选型
方案 A:数据库原子认领
最初的想法是用本地数据库做原子认领——UPDATE videos SET claimed_by = ? WHERE claimed_by IS NULL LIMIT 1 之类。但问题是:云端 CI 容器访问不了本地 NAS 的数据库端口。云构建环境没有直接回本地内网的路由。
方案 B:本地调度分配
本地跑一个 orchestrator,定期扫描并将文件清单分配给各个处理器。缺点是一旦引入调度器,就区分了”调度方”和”工作方”,违反了”不区分处理链路”的设计要求。
方案 C:Serverless Redis 原子认领(最终方案)
需要一个双方都能访问的中间件。云服务端和本地都能通过 HTTPS 访问 Redis——这就是 Upstash 的 Serverless Redis。
它提供一个 HTTP REST 接口,直接通过 curl 发 Redis 命令。最关键的指令是 SETNX(SET if Not eXists),正好用来实现原子认领。

Upstash Redis:免注册就能用
Upstash 的免费实例可以直接通过一条 curl 命令获得:
curl -X POST https://upstash.com/start-redis
返回 REST URL 和 Token,不需要绑卡,不需要注册流程。免费实例的规格是 256MB、每月 50 万条命令,对于做分布式锁来说绰绰有余。3 天有效,在控制台页面可以 Claim 保留。
TLS 加密传输,Token 认证,REST API 直接存在环境变量的配置文件中。
实现
核心逻辑就是一个原子认领函数:
def claim_file(filename, processor_id, ttl=3600):
key = f"claim:video:{filename}"
# SETNX = 仅 key 不存在时才设置,返回 1/0
r = redis_cmd(["SETNX", key, processor_id])
if r == 1:
redis_cmd(["EXPIRE", key, str(ttl)])
return True
return False
处理器的启动流程:
- 生成一个唯一的
PROCESSOR_ID(用 CI 构建 ID 或随机 UUID) - 启动时释放自身残留的 claim(防止上一次崩溃留下脏数据)
- 对每个要处理的文件,先
claim_file - 未认领到(被别人先拿了)就跳过
- 处理完成后显式释放 claim
这个模式本质上就是分布式锁的经典做法:用 Redis 做协调层。TTL 是安全网,防止处理器崩溃后锁永远不释放。
踩坑记录
1. Upstash REST API 的数组传参
Upstash 的 REST API 用 POST 发送命令,格式必须是 JSON 数组:
["SETNX", "key", "value"]
不能拼接成字符串。SCAN 命令尤其容易踩坑——参数要拆开传:
["SCAN", "0", "MATCH", "claim:video:*", "COUNT", "500"]
另外,Upstash 不支持 EVAL/LUA 脚本,所以不能使用复杂的 Lua 逻辑。所有操作都要拆成原子命令组合。
2. 循环周期的误判
最初以为摄像头的循环周期是 1 个月——这意味着 1 个月左右旧数据就会开始被覆盖。实际上检查后发现是 1 年。
这个误判导致策略完全不同。如果真的是 1 个月,要处理的数据量远小于现在的 3.6TB。知道是 1 年后,才有余裕做精细的规划:先处理到某个时间点,再调整循环周期。
3. 降级安全网
如果 Redis 服务不可达(比如网络故障或服务临时下线),处理器应该继续工作而不是阻塞。实现上很简单——在调用 claim 时捕获所有异常,Redis 不可用时降级为”不检查,直接处理”:
try:
if not claim_file(name, processor_id):
return # 跳过
except Exception:
# Redis 不可用时降级为直接处理
pass
这不是偷懒的设计选择。对于文件处理这种业务来说,偶尔重复处理比经常不处理更安全。重复处理只是浪费计算资源,但不处理可能导致数据丢失。如果必须二选一,选择前者。
空间管理策略
理清现状后,制定了三步走计划:
- 先处理到 2025 年 12 月 — 大约 3 个月的数据量。把这段时间的关键视频片段截取完、入库
- 到达目标后,把循环周期从 1 年改为 6 个月 — 这样可以释放约 1.8TB 的空间
- 继续往前推进,直到所有有价值的录像都被处理过
最终建成一个定时任务,每日扫描新视频,处理完即标记,不再保留原文件。
总结
回头看,这个项目的关键决策其实就两个:
1. 用一个轻量级的中间件做分布式协调 — Upstash Redis 的 SETNX 命令完美贴合这个需求:零部署、零维护、双端可访问。比起自建 ZooKeeper 或者暴露本地数据库端口,Serverless Redis 的”HTTP 发几条命令就能用”确实省心。
2. 降级优先于可用性 — 分布式锁只是一个优化手段。当它工作时,避免重复处理;当它失效时,系统照常运行。不会因为要等一个锁而把处理流水线卡死。
最终处理完 3.6TB 数据并不是最难的部分。最有价值的收获是把这套”多路并发、原子认领、降级安全”的模式固定下来,以后任何需要多节点处理文件流的场景,都可以直接复用。