Redis 超时并不是普通报错,而是服务端直接强制断开连接;因此客户端需要重点捕获网络层异常。想实现优雅的断点续传,关键在于拆分任务、原子更新偏移量,并确保集群环境中的 slot 一致。

超时不是报错,而是连接被硬杀
Redis 一旦发生超时,通常不会返回一个 ERR,甚至不会给出任何正常响应,而是直接关闭 socket 连接。客户端侧常见到的现象就是 ConnectionResetError(Python)、ECONNRESET(Node.js),或者类似 “broken pipe” 的底层网络异常。这里非常容易判断失误:并不是脚本只是还没执行完成,而是 Redis 已经主动中断连接。也正因如此,试图通过捕获 redis.call() 的错误来处理超时问题,实际上并不可行。
常见误区是:在 Lua 中使用 pcall(redis.call(...)) 包裹操作,以为这样就能兜住超时异常;但事实上,超时发生在 Redis 主线程内部,Lua 解释器往往已经被终止,pcall 根本没有执行机会。
- 服务端日志通常会记录
Script attempted to execute a command that would exceed the configured timeout(需 loglevel ≥ notice) - 客户端必须监控和处理网络层异常,而不是只关注命令级报错
- 重试之前一定要确认脚本具备幂等性——例如使用
SET key val NX EX 60替代无条件的SET
用 redis.call() “续命”不等于安全续传
每次调用 redis.call() 或 redis.pcall(),确实都会重置 lua-time-limit 计时器,但这只是在延长脚本存活时间,并不等于真正实现了断点续传。更准确地说,这种方式只是掩盖了架构设计上的问题:把原本应该拆开的长任务,硬塞进了一个 Lua 脚本里。
比如遍历 10 万个 key 并逐个处理,在脚本中插入 100 次 redis.call("PING") 的确可能避免超时被杀,但整个脚本依旧会长时间占用 Redis 主线程,持续数十秒阻塞其他请求,性能风险并没有消失。
- 真正可恢复、可续传的做法是:将大任务拆分成多个小脚本,每次只处理固定数量(如 100 个)key,并使用唯一 token 标记执行进度
- 使用
INCR或HINCRBY维护已处理 offset,执行失败后可从下一个 offset 继续恢复 - 尽量避免在脚本内进行纯计算型长循环;如果必须循环,加入
redis.call("PING")也只能作为兜底措施,不能当作核心方案
断点续传状态必须存 Redis,且结构要支持原子更新
断点续传依赖外部状态存储,而这份状态本身不能成为新的瓶颈,也不能因为并发导致进度丢失。将状态放在 Redis 中是常见方案,但不要使用 GET/SET 两步更新——在并发竞争场景下,这样很容易产生竞态条件并丢失偏移量。
更推荐的组合方式是:用 HSET 保存任务元信息,用 INCR 更新偏移量,再通过 EXPIRE 设置过期时间。例如:
HMSET task:abc status "running" total 100000 started_at 1720851600 INCR task:abc:offset
关键要点:
- 使用
HSETNX初始化任务,防止任务被重复提交或重复创建 - 如果业务对强一致性要求较高,可以通过
WATCH+MULTI包裹状态变更流程 - 所有 key 命名都应带上唯一 task ID,避免多个任务之间相互污染
- 客户端每次执行前应先
GET task:abc:offset,再判断从哪里继续,而不是完全依赖脚本内部自增
集群环境下续传要严格限定 slot
在 Redis Cluster 环境中,Lua 脚本不支持跨 slot 执行。如果任务涉及多个 key,同时又希望支持断点续传,就必须保证所有相关 key 都落在同一个 hash slot 中——否则脚本根本无法正常执行,更谈不上超时后的恢复和续传。
验证方法可以使用:CLUSTER KEYSLOT key_name 查看每个 key 对应的 slot,并通过 {} 指定强制哈希标签(例如 user:{123}:profile 和 user:{123}:settings 会落在同一个 slot)。
- 不要在续传脚本中动态拼接 key 名却不校验 slot,否则可能首次执行成功、后续续传失败
- 优先使用
EVALSHA而不是EVAL,减少大脚本传输带来的网络开销和延迟 - 脚本中的所有
KEYS都必须显式传入,不能依赖字符串拼接生成,否则在 Cluster 模式下容易解析失败
真正的难点并不只是让 Lua 脚本顺利跑完,而是要让中断后的任务状态可识别、可恢复,并且不完全依赖 Redis 单点内存。Redis 超时只是一个表象,它暴露出的本质问题通常来自任务拆分粒度、状态持久化设计以及集群 slot 约束三方面——其中任何一项处理不到位,所谓断点续传就很容易变成不可控的“玄学问题”。
