n8n 同一条消息处理两次,怎样用事件 ID 避免重复建单
同一条消息在 n8n 里运行两次,不一定是节点连错。发送方没有收到及时响应、网关重传、人工重放,都可能让相同业务事件再次到达。可靠的处理方式是让业务系统识别事件身份,并把“允许创建单据”变成一次可核对的状态转换。本文适合接收消息后建工单、建线索的团队,使用合成输入解释方案,未在你的 n8n 实例执行测试。
先确定谁提供事件身份
先向发送方索要稳定的事件 ID,确认同一事件重试时不会换号。不能把 n8n 执行编号当作去重依据,因为重传会产生新执行。若不同客户可能产生相同事件号,键应同时包含租户、来源和事件号,例如 tenant_demo:service:event_101。对确实没有事件号的旧接口,才讨论业务键与载荷摘要的组合,并明确同一天两次真实报修不能被误判为重复。
不要直接对整段消息做哈希后永久去重。消息中的发送时间或签名变化,会让重复事件拥有不同摘要;两次内容相同的合法操作也可能被吞掉。事件身份回答“是不是同一件事”,载荷摘要回答“同一事件的内容有没有变化”,两者应该分列。遇到同 ID 不同摘要,先隔离为冲突,交给责任人核对来源,不能覆盖第一份输入继续建单。
状态表比一个已处理开关更有用
为每个事件记录事件键、摘要、状态、领取者、租约到期时间、业务单号和最后错误。建议状态至少有待处理、处理中、已完成、结果未知、待人工。收到事件时,通过有唯一约束的持久存储抢占事件键,只有抢占成功的执行进入建单分支。先查再插的两个独立步骤存在并发窗口,两个执行可能同时查到不存在,因而都创建单据。
以 PostgreSQL 为例,唯一约束配合 INSERT ... ON CONFLICT 可以让冲突处理发生在数据库写入时;具体事务与权限仍需按目标数据库设计,参见官方 INSERT 说明。这不是 n8n 自动提供的业务级“恰好一次”保证。流程图里多一个判断节点,也不能替代数据库原子性或下游接口支持的幂等键。
用三次回调走完一条业务链
下载重复事件演练表,其中全部是合成数据。第一次 event_101 领取成功,创建 ticket_demo_01 并写回已完成;第二次相同 ID 与摘要到达,直接读取原单号返回;第三条 event_102 是新的报修,即使描述相同,也应走新任务。业务写入前还要验证请求人有建单权限,金额、收件人或责任归属等高风险变更必须保留人工确认。
演练时先让下游只写测试账本,不接真实业务。把同一输入连续发送两次,再并发发送两次,最后在建单成功但回写状态之前制造中断。预期不是所有执行都显示成功,而是账本中同一事件键只有一个业务单号,后续请求可以追到原结果。每次核对输入条数、不同事件数、成功单据数以及未知事件数,不用执行总数推算业务总量。
最容易漏掉的是成功后的断线
如果下游已经建单,但 n8n 超时,没有收到单号,此时不能把事件恢复为待处理并再次创建。先把它记为结果未知,通过原事件键或业务请求号查单;查到后补记结果,查不到也要确认查询是否具有足够一致性。只有目标系统明确证明未执行,才可以按约定重新尝试。没有状态查询能力时,应保留人工核对入口,不能承诺无人值守恢复。
处理中状态也不能永久占住事件。需要租约或超时审查,但租约过期不等于业务动作失败;接管者仍需查下游结果。去重记录的保留时间应覆盖发送方重传窗口、人工重放窗口与业务追溯需要,清理前留必要的摘要与单号映射。超过窗口的旧事件应转入检查队列,避免把历史回调当作今天的新指令。
把验收写成可以反驳的条件
验收材料要包含重复、并发、同号异内容、写后断线和存储不可用五类输入。存储故障时应暂停写操作或进入人工处理,不能“去重失败就继续”。同时验证重复请求返回的信息不会泄露另一个租户的单据。业务人员应能从事件键找到原始请求摘要、确认记录和目标单号,而不是只能看到技术堆栈。
业务方还要确定“重复”的粒度。售后消息可能先发创建事件,再发内容补充事件,两者共享工单号却不是同一事件;补充应该更新原工单,而不是被创建去重逻辑吞掉。将事件类型纳入状态机,并限制每种事件允许改动的字段。迁移发送方或更换渠道时,事件号的命名空间也可能变化,需保留映射与切换日期。验收时专门加一条补充事件和一条过期历史事件,由业务人员判断预期结果。对用户的回执也应明确是首次创建、重复已处理还是等待核对,不要把三种情况都写成笼统的成功。这样客服能够解释为何消息到了两次,系统却只有一张单。
了解 n8n 的基础边界可看n8n 专题和AI Agent 节点适合做什么;涉及超时未知结果,可继续读工具超时后的核对流程。如果你需要把现有消息渠道接到工单或 ERP,带上脱敏回调、目标接口和重传规则,通过定制咨询讨论去重状态表与验收范围。