DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Lua 已结束,旧 redis-py pipeline 为什么还报 BUSY

区分活动脚本与旧客户端响应错位,核对2.8.0修复和真实写入结果,不盲目重发流水线。

长 Lua 脚本已经返回,另一个新连接也能正常执行命令,但 Python 进程的 pipeline.execute() 仍反复报 BUSY Redis is busy running a script。如果这是旧 redis-py 的事务流水线路径,要检查客户端是否在此前 MULTI 报错后,没有读完同一连接上的后续响应。

这是一个已在 redis-py 2.8.0 修复的历史问题。2.7.6 的相应实现遇到 MULTI 的 ResponseError 会提前退出读取;不能据此认为当前所有 redis-py 版本仍有同样的缺陷,也不能仅凭一次 BUSY 就判断脚本已经结束。

同一个错误,可能来自不同的阶段

Redis 正在执行超过忙碌阈值的 Lua 脚本时,其他普通命令确实可能收到 BUSY。这个阈值不会自动杀掉脚本,客户端自己超时也不能证明服务端已经停止执行。

旧客户端的另一条路径发生在这个忙碌阶段之后。事务流水线会发送 MULTI、缓冲的命令和 EXEC,再逐个读取响应。如果读到 MULTI 的错误就直接抛出异常,后面的响应可能留在连接中。连接被继续复用时,后续请求就可能读到不对应当前请求的错误。

因此要分开保存第一条 BUSY、脚本结束时间、客户端实际版本与完整 traceback。通过同一目标实例的受控新连接核对当前状态,同时确认没有连到另一个节点或数据库。若服务端仍然忙,按活动脚本处理;只有原进程继续报错的现象,还需核对连接复用与流水线实现。

不要在生产为了复现而运行长循环脚本。观察命令执行日志也需要权限和数据保护,不能随意长期开 MONITOR 来截取其他业务内容。

报异常,不足以证明流水线没有写入

在响应顺序已经出问题的路径中,客户端报 BUSY 时,服务端仍可能执行了后来的流水线。业务收到异常与业务写入未发生,是两件需要分别核对的事。

停止对同一批写入无条件重试,按业务 ID、受影响键和已有幂等记录确认实际结果。不要把监控里出现 MULTI、SET、EXEC 当成所有业务写入都完整成功的证明;还要检查执行结果及具体数据。

这也是不能把异常统一转成“重发整个 pipeline”的原因。即使眼前只是一条 SET,实际流水线可能还有计数、消费确认或其他重复执行会改变结果的操作。脚本缓存缺失的流水线也有自己的重试边界,可对照Redis Cluster 的 NOSCRIPT 路由与补载。

修复客户端,并处理受影响的旧连接

redis-py 2.8.0 对这条路径的修复,是在解析 MULTI 的 ResponseError 后继续消费后续命令和 EXEC 的响应,再按实际错误处理。对于仍使用受影响旧版本的应用,按项目依赖兼容范围升级到包含修复的版本,并核对锁定版本及真正启动进程加载的包。

切换客户端版本不会让已经运行的进程自动换包,也不能证明旧池中每条连接的响应状态都已经恢复。按应用连接池生命周期排空、替换受影响进程或连接,先核对尚未完成的业务,避免为了重启而重复投递同一批任务。

如果使用的是较新版本,先保留版本和 traceback,检查是否仍经过相同实现,或者服务端实际上又有活动脚本。不要为了套用历史答案降级,也不要只延长客户端超时来掩盖响应错位。

SCRIPT KILL 只处理仍运行且尚未写入的脚本

脚本确实仍在运行、并确认没有执行过写操作时,获准的管理操作可以使用 SCRIPT KILL 中止它。已经写过数据的脚本不能按这个方式安全中断;这个命令也不会修复已经结束脚本之后留下的客户端响应状态。

不要把停止 Redis 进程且不保存数据的命令作为常规修复。它会影响整个实例及持久化结果,需要独立的事故处置判断。

修改前保存依赖、进程与连接管理配置,在隔离环境检查脚本忙碌期间的错误处理,以及脚本结束后的新连接和池复用。验收还要核对原流水线的业务结果,确认没有因异常重试造成重复写入。软件回退不能撤销已经发生的写操作,数据恢复依据业务记录或备份处理。