site logo

Marico's space

重试不等于恢复:Odoo 集成的实用故障模型

编程技术 2026-09-30 20:55:56 7

最近给客户做两个 Odoo 18 实例之间的集成,踩了不少坑。这篇把一个核心问题说清楚:重试(Retry)不等于恢复(Recovery)。

一个集成任务失败了,第一反应往往是:放回队列重跑。

这可能修复一个临时网络故障。但也可能产生重复订单、重复发货更新,或者触发一个已经发生的外部副作用。

直接说结论:重试是重复执行一个操作;恢复是让系统回到已知的、一致的状态。恢复可能需要重试,但更多时候需要回放存储的证据、协调两个系统、或者停下来让业务人员做决策。

这个区别在 Odoo 集成中格外重要,因为本地事务边界无法描述一次 HTTP、REST 或 XML-RPC(远程过程调用)调用在另一端到底发生了什么。

队列是执行机制,不是恢复模型

OCA 的 queue_job 文档描述了后台任务、隔离事务、重试模式、通道和任务依赖。这些都是很有价值的构建块,让集成可以推迟工作、控制并发、在遇到可重试异常后重试任务。

但队列本身无法推断一个失败背后的业务含义。

假设一个 Odoo 任务向另一个系统发送创建请求。接收方提交了新记录,但响应在网络传输中丢失了。Odoo 看到超时,把任务标记为失败。从队列的角度,再跑一次技术上是正确的。但从业务角度看,可能产生了一条重复数据。

选择动作之前,先分类故障,搞清楚可能已经发生了什么。

一个实用的故障分类法

我把以下分类作为操作工具,不是对 Odoo 内部异常层次结构的描述。

故障类别 典型证据 安全的自动操作 主要风险
传输层故障 超时、连接重置、DNS 解析失败 检查副作用后再重试 远程系统可能在响应丢失前已经提交
认证故障 凭证过期或被拒绝 停止,修复凭证,然后谨慎恢复 重复失败、账户锁定或身份错误
字段不匹配 缺少字段、类型错误、载荷结构异常 停止,修复契约或映射关系 数据损坏或不完整
API 契约不匹配 模型或方法不存在,签名已变更 停止,验证已部署的 API 重试一个永远不可能成功的请求
源数据错误 缺少必需的业务数据 修正源记录,然后重放 绕过验证逻辑,传播错误数据
业务冲突 状态转换不再被允许 协调或转给人工处理 覆盖一个有效的新状态
部分处理 部分步骤已提交,后续步骤失败 检查检查点并协调 重复数据或矛盾状态

这张表的目的不是术语定义,而是防止一个"重试"按钮掩盖了七种完全不同的操作决策。

重试、回放、协调:回答不同的问题

重试:同样的操作现在能再跑一次吗?

重试在临时问题解决后,用相同的输入重复执行相同的操作。它适用于失败原因可能是临时的、且重复执行是安全的情况。

"安全"需要证据。读操作通常很简单。写操作需要先问自己:

  1. 远程端可能已经提交了吗?
  2. 能识别出之前的结果吗?
  3. 接收方是否强制执行了唯一性或幂等性规则?
  4. 哪些下游副作用会再次触发?

指数退避可以降低负载。但它回答不了以上任何问题。

回放:修复后能重新处理原始证据吗?

回放是在修正数据、映射、配置或代码后,从存储的事件、命令或源快照重新开始处理。它不仅仅是"再跑一次失败的方法"。

受控的回放需要:

  • 原始输入或对它的不可变引用;
  • 当时使用的契约版本;
  • 已知的重启点;
  • 检查现有结果的逻辑;
  • 将回放与原始尝试关联的审计轨迹。

回放适用于确定性故障。字段改名了或映射修复了,不会因为反复重试就自动变正确,但修复后通常可以成功处理原始事件。

协调:两个系统现在达成一致了吗?

协调是在不信任任务历史为完整真相的前提下,比较跨系统的实际状态。

这就是能发现"远程存在但本地任务显示失败"的订单、"从未返回的发货状态"、或"部分处理期间创建的记录"的过程。协调流程通常匹配稳定标识符、比较相关字段或状态、分类差异,然后要么修复,要么创建人工处理任务。

重试处理执行尝试。协调处理实际情况。

外部标识符有帮助,但不是幂等性

外部标识符在不同系统的记录之间创建了持久关联。它让集成可以问:"我是否已经为这条源记录创建了相关业务对象?"

这对于防止重复是必要的,但还不够。形式上的幂等性取决于在哪里强制执行唯一性、查询和创建是否原子化、并发请求如何表现、以及哪些副作用发生在保护边界之外。

例如,以下序列仍然存在竞态条件:

  1. 在接收方搜索外部 ID X。
  2. 没找到。
  3. 发送创建请求。

两个 worker 可能在任何一方创建记录前都通过了第 2 步。数据库唯一性约束或接收方侧的幂等性 key 关闭了预检查本身无法关闭的边界。

用外部标识符定位之前的结果、关联日志。除非完整的写路径支持这样的声明,否则不要把它们描述为完整的幂等性保证。

两个 Odoo-to-Odoo 集成中的真实案例

在 kt.team 工作期间,我参与了一个面向 SPL 的双 Odoo 18 Community 实例集成的架构设计和开发。一个实例代表 marketplace 端,另一个处理供应商执行端。订单、采购、发货、库存数据、消息和附件通过独立模块和队列任务流转。

两个脱敏案例说明为什么分类很重要。

字段不匹配:重试修不了

一个库存同步任务期望的是 stock.move.line.qty_done,而部署的环境在这个特定场景下使用的是 quantity。这不是所有 Odoo 安装的通用规则;这是集成假设与该环境实际可用模型之间的不匹配。

这个任务是确定性的。每次重试都在重复同样的错误假设。正确的响应是检查部署的模型、修复映射关系,然后回放受影响的任务。

目标上不存在的 API 方法

另一个任务试图调用 product.product 上 marketplace 端 API 中并不存在的方法。连接和认证都正常,是契约写错了。

同样,重试没用。集成需要使用目标端实际暴露的库存更新方法,然后在受控条件下重新处理受影响的记录。

两个案例都不是"队列问题"。队列只是暴露了故障、保存了工作,但恢复取决于对部署契约的理解。

六步恢复决策

当 Odoo 集成任务失败时,我用这个顺序:

  1. 保存证据。 保留任务标识符、源记录、关联数据、错误类别和相关请求元数据。不要在日志中暴露凭证或个人载荷。
  2. 分类故障。 区分为传输层、认证、字段、API 契约、源数据、业务冲突和部分处理。
  3. 确定副作用。 判断在故障可见之前,本地和远程可能已提交了什么。
  4. 定位已有结果。 在再次创建任何内容前,先用外部标识符和接收方侧查询。
  5. 选择操作。 对临时安全的情况重试,修复后回放保存的证据,或协调实际状态。业务冲突要升级,不要覆盖。
  6. 验证最终状态。 任务变绿不够。确认两个系统现在对重要的业务事实达成一致。

同样的模型适用于 REST、XML-RPC 或其他协议。协议错误是可观察的症状;恢复决策取决于业务状态和副作用。

为故障路径设计

可恢复性是架构属性。它来自稳定的标识符、明确的所有权、保存的输入、可观察的检查点、受约束的副作用,以及一条协调路径。

队列和重试策略仍然有用。它们只是设计的一层。

如果唯一的恢复指令是"重试失败的任务",那系统还没有恢复模型。只有一个执行机制和一个假设。