
最近给客户做两个 Odoo 18 实例之间的集成,踩了不少坑。这篇把一个核心问题说清楚:重试(Retry)不等于恢复(Recovery)。
一个集成任务失败了,第一反应往往是:放回队列重跑。
这可能修复一个临时网络故障。但也可能产生重复订单、重复发货更新,或者触发一个已经发生的外部副作用。
直接说结论:重试是重复执行一个操作;恢复是让系统回到已知的、一致的状态。恢复可能需要重试,但更多时候需要回放存储的证据、协调两个系统、或者停下来让业务人员做决策。
这个区别在 Odoo 集成中格外重要,因为本地事务边界无法描述一次 HTTP、REST 或 XML-RPC(远程过程调用)调用在另一端到底发生了什么。
OCA 的 queue_job 文档描述了后台任务、隔离事务、重试模式、通道和任务依赖。这些都是很有价值的构建块,让集成可以推迟工作、控制并发、在遇到可重试异常后重试任务。
但队列本身无法推断一个失败背后的业务含义。
假设一个 Odoo 任务向另一个系统发送创建请求。接收方提交了新记录,但响应在网络传输中丢失了。Odoo 看到超时,把任务标记为失败。从队列的角度,再跑一次技术上是正确的。但从业务角度看,可能产生了一条重复数据。
选择动作之前,先分类故障,搞清楚可能已经发生了什么。
我把以下分类作为操作工具,不是对 Odoo 内部异常层次结构的描述。
| 故障类别 | 典型证据 | 安全的自动操作 | 主要风险 |
|---|---|---|---|
| 传输层故障 | 超时、连接重置、DNS 解析失败 | 检查副作用后再重试 | 远程系统可能在响应丢失前已经提交 |
| 认证故障 | 凭证过期或被拒绝 | 停止,修复凭证,然后谨慎恢复 | 重复失败、账户锁定或身份错误 |
| 字段不匹配 | 缺少字段、类型错误、载荷结构异常 | 停止,修复契约或映射关系 | 数据损坏或不完整 |
| API 契约不匹配 | 模型或方法不存在,签名已变更 | 停止,验证已部署的 API | 重试一个永远不可能成功的请求 |
| 源数据错误 | 缺少必需的业务数据 | 修正源记录,然后重放 | 绕过验证逻辑,传播错误数据 |
| 业务冲突 | 状态转换不再被允许 | 协调或转给人工处理 | 覆盖一个有效的新状态 |
| 部分处理 | 部分步骤已提交,后续步骤失败 | 检查检查点并协调 | 重复数据或矛盾状态 |
这张表的目的不是术语定义,而是防止一个"重试"按钮掩盖了七种完全不同的操作决策。
重试在临时问题解决后,用相同的输入重复执行相同的操作。它适用于失败原因可能是临时的、且重复执行是安全的情况。
"安全"需要证据。读操作通常很简单。写操作需要先问自己:
指数退避可以降低负载。但它回答不了以上任何问题。
回放是在修正数据、映射、配置或代码后,从存储的事件、命令或源快照重新开始处理。它不仅仅是"再跑一次失败的方法"。
受控的回放需要:
回放适用于确定性故障。字段改名了或映射修复了,不会因为反复重试就自动变正确,但修复后通常可以成功处理原始事件。
协调是在不信任任务历史为完整真相的前提下,比较跨系统的实际状态。
这就是能发现"远程存在但本地任务显示失败"的订单、"从未返回的发货状态"、或"部分处理期间创建的记录"的过程。协调流程通常匹配稳定标识符、比较相关字段或状态、分类差异,然后要么修复,要么创建人工处理任务。
重试处理执行尝试。协调处理实际情况。
外部标识符在不同系统的记录之间创建了持久关联。它让集成可以问:"我是否已经为这条源记录创建了相关业务对象?"
这对于防止重复是必要的,但还不够。形式上的幂等性取决于在哪里强制执行唯一性、查询和创建是否原子化、并发请求如何表现、以及哪些副作用发生在保护边界之外。
例如,以下序列仍然存在竞态条件:
两个 worker 可能在任何一方创建记录前都通过了第 2 步。数据库唯一性约束或接收方侧的幂等性 key 关闭了预检查本身无法关闭的边界。
用外部标识符定位之前的结果、关联日志。除非完整的写路径支持这样的声明,否则不要把它们描述为完整的幂等性保证。
在 kt.team 工作期间,我参与了一个面向 SPL 的双 Odoo 18 Community 实例集成的架构设计和开发。一个实例代表 marketplace 端,另一个处理供应商执行端。订单、采购、发货、库存数据、消息和附件通过独立模块和队列任务流转。
两个脱敏案例说明为什么分类很重要。
一个库存同步任务期望的是 stock.move.line.qty_done,而部署的环境在这个特定场景下使用的是 quantity。这不是所有 Odoo 安装的通用规则;这是集成假设与该环境实际可用模型之间的不匹配。
这个任务是确定性的。每次重试都在重复同样的错误假设。正确的响应是检查部署的模型、修复映射关系,然后回放受影响的任务。
另一个任务试图调用 product.product 上 marketplace 端 API 中并不存在的方法。连接和认证都正常,是契约写错了。
同样,重试没用。集成需要使用目标端实际暴露的库存更新方法,然后在受控条件下重新处理受影响的记录。
两个案例都不是"队列问题"。队列只是暴露了故障、保存了工作,但恢复取决于对部署契约的理解。
当 Odoo 集成任务失败时,我用这个顺序:
同样的模型适用于 REST、XML-RPC 或其他协议。协议错误是可观察的症状;恢复决策取决于业务状态和副作用。
可恢复性是架构属性。它来自稳定的标识符、明确的所有权、保存的输入、可观察的检查点、受约束的副作用,以及一条协调路径。
队列和重试策略仍然有用。它们只是设计的一层。
如果唯一的恢复指令是"重试失败的任务",那系统还没有恢复模型。只有一个执行机制和一个假设。