Serverless Web3 Webhook:链上事件回调要能去重和补偿
Serverless Web3 Webhook:链上事件回调要能去重和补偿
DApp 后端经常用 Serverless 接收链上事件 webhook,比如交易确认、NFT 转移、合约事件。Serverless 很方便,但链上事件回调可能重复、乱序、延迟,甚至漏投。函数能触发不代表事件处理可靠。
Web3 webhook 的核心是去重、顺序和补偿,不是写一个 handler。
在实际工程中,Web3 webhook 的最大挑战是"链上事件的确定性"和"webhook 交付的不确定性"之间的矛盾。链上事件本身是确定的(一旦确认,就不会变),但 webhook 交付是不确定的(可能重复、可能丢失、可能延迟)。如果系统假设"webhook 一定会送达,且只送达一次",就会在数据一致性上出问题。工程上更稳健的做法是:把 webhook 当成"事件通知",而不是"事件本身"。收到 webhook 后,先去链上确认事件是否真的存在、是否已经确认足够区块,再决定是否处理。这样即使 webhook 重复或丢失,也不会影响数据正确性。
更深层的问题是:Serverless 的"无状态"特性和链上事件的"有状态"特性之间的冲突。链上事件是连续的(按区块高度、交易索引、log 索引排序),但 Serverless 函数是无状态的,每次执行都是独立的。如果多个函数同时处理不同事件,可能会因为竞态条件导致状态不一致(如两个函数同时更新同一个用户的余额)。生产级系统需要设计"事件排序"或"状态锁":要么按事件顺序串行处理(如用队列),要么在更新状态时加乐观锁(版本号或 CAS)。无状态函数处理有状态事件,必须在架构层面解决一致性问题。
一、事件处理链路
flowchart TD A[Chain Event] --> B[Webhook Provider] B --> C[Serverless Function] C --> D[Idempotency Check] D --> E[Event Store] E --> F[Business Handler]先落事件,再处理业务。不要在函数里直接改业务状态后就结束。
二、事件 ID 要唯一
链上事件可以用 chainId、txHash、logIndex 组合成唯一键。
const eventId = `${chainId}:${txHash}:${logIndex}`;写入事件表时用唯一索引,重复回调直接跳过。
三、处理要幂等
CREATE TABLE chain_events ( event_id TEXT PRIMARY KEY, status TEXT NOT NULL, payload JSONB, created_at TIMESTAMP );业务处理失败时,把状态标记为 failed,后续由补偿任务重试。不要只靠 webhook provider 重试。
四、漏事件要能补扫
定时任务按区块高度补扫,和 webhook 数据对账。
reconcile: from_block: last_confirmed_block to_block: current_block - confirmations compare_event_store: true链上事件系统没有补偿扫描,就不适合承载关键业务。
在生产环境中,补扫区块的一个常见踩坑是"补扫和 webhook 的重复处理"。比如补扫任务正在扫描区块 100-200,同时 webhook 也在推送区块 150 的事件,就可能同一个事件被处理两次。工程上需要做"幂等性保护":事件 ID 唯一索引、处理状态检查、或者给补扫任务加锁(如 Redis 分布式锁),确保同一时间只有一个任务在处理某个区块范围。
另一个边界场景是"链重组导致的事件失效"。以太坊等区块链可能发生链重组(reorg),导致之前确认的事件变成未确认,甚至被替换。如果系统已经处理了某个事件,但后来链重组了,这个事件可能就从链上消失了。生产级系统需要设计"链重组检测和处理":定期对比本地事件表和链上事件,如果发现本地有但链上没有的事件,就标记为"已回滚",并回滚对应的业务状态。这种处理在技术上不复杂(用区块哈希对比即可),但很多团队在早期设计时忽略,等到真的遇到重组时才发现问题。
还要考虑区块确认数。刚监听到事件不代表最终确定,链重组可能让事件失效。关键业务通常要等若干确认后再进入最终状态。
confirmation_policy: ethereum_mainnet: 12 testnet: 3 high_value_transfer: 24Serverless 函数可以先记录 pending,再由确认任务把事件推进 confirmed。这样状态变化更符合链上现实。
如果 webhook provider 延迟或失败,补扫任务必须能从上次确认区块继续。不要把可靠性完全寄托在第三方回调。
五、总结
Serverless 处理 Web3 webhook 时,要用 chainId、txHash、logIndex 去重,先落事件,再幂等处理,并通过区块补扫做补偿。
函数触发只是开始。事件可靠,DApp 状态才可靠。
Web3 后端最怕“偶尔漏一条”。链上事件少一条,用户资产状态就可能错一片。去重、确认、补偿缺一不可。
Serverless 还要注意执行时间限制。补扫区块、批量处理事件、拉取链上详情都可能超过函数超时。长任务应该拆成队列任务,函数只负责接收和入队。
serverless_event_split: webhook_function: validate_and_enqueue worker: process_event reconciler: scan_missing_blocks这样即使某次处理变慢,也不会让 webhook provider 认为接口不可用。
事件 payload 也要原样保存一份。业务字段解析错了,还可以从原始 payload 重新回放。没有原始事件,补偿会少一条退路。
