自定义事件重复触发本质是响应逻辑缺乏幂等性;需通过唯一标识标记已处理事件、副作用操作幂等化、源头控制重复生成及设计可重放处理器来解决。

自定义事件重复触发不是事件本身的问题,而是事件响应逻辑缺乏幂等性。核心思路是:让同一事件的多次触发,产生与单次触发完全一致的结果。
识别并标记已处理的事件
在事件处理开始时,立即记录该事件的唯一标识(如 event.id、业务单号 + 事件类型 + 时间戳哈希),并检查是否已存在。推荐使用内存缓存(如 Map 或 WeakMap)或分布式缓存(如 Redis)存储处理状态。
- 前端场景可用
Map缓存事件 ID,5~10 秒后自动清理,兼顾性能与内存 - 后端服务建议用 Redis 的
SET key value EX 60 NX命令,原子性保证“设置成功即首次处理” - 避免仅依赖客户端时间戳或随机数作为 ID,应结合业务主键(如 order_id + "paid")确保语义唯一
将副作用操作转为幂等执行
即使事件被重复分发,关键操作(如扣库存、发通知、更新状态)也必须支持重复调用不破坏数据一致性。
- 状态更新用“设为终态”代替“递增/递减”,例如
UPDATE order SET status = 'shipped' WHERE id = ? AND status != 'shipped' - 数据库写入优先使用
INSERT ... ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL)防重复记录 - 发送消息类操作,先查本地消息表是否已发过同业务ID的消息,再决定是否投递
在事件源头控制重复生成
很多重复触发源于上游:网络重传、定时任务误触发、消息队列重复投递(如 Kafka 消费者崩溃后 offset 回滚)。需协同治理。
- 生产端发送事件前,生成带业务上下文的全局唯一 ID(如雪花 ID + 业务类型前缀),并记录日志供追溯
- 使用支持“恰好一次”语义的消息中间件(如 Kafka 配合幂等 Producer + 事务),或启用 RocketMQ 的消息去重策略
- 对 HTTP webhook 类事件,要求调用方携带
X-Request-ID,服务端将其纳入幂等键计算
设计可安全重放的事件处理器
把事件处理器当作纯函数来设计:输入确定 → 输出确定 → 副作用可控。不依赖隐式状态,所有判断基于事件载荷和当前业务实体快照。
- 处理前先查最新业务状态(如查订单当前是否已发货),再决定是否执行后续动作
- 避免在 handler 中做“先查再改”的非原子操作;改用数据库行锁、乐观锁(version 字段)或 CAS 更新
- 记录结构化日志,包含事件 ID、处理结果(success/skipped)、跳过原因(如 “already shipped”),便于监控与排查










