分布式环境下如何确保事件仅被单进程处理一次?
分布式环境下事件仅处理一次的无新增组件方案
嘿,这个问题我之前做分布式系统落地时也碰到过——多节点跑同一份代码,就怕同个事件被重复触发处理,还不想平白加MQ这类额外组件对吧?给你几个基于现有技术栈就能实现的实用方案:
1. 基于现有数据库的幂等标记+锁
这是最通用的方案,毕竟几乎所有系统都离不开数据库:
- 给每个事件分配唯一标识(比如全局事件ID、业务唯一键如
订单ID+操作类型),在数据库里建一张事件处理表,把这个唯一标识设为唯一索引。 - 处理事件前,先执行
INSERT INTO event_process (event_id, status) VALUES ('xxx', 'processing') ON DUPLICATE KEY UPDATE status = status:如果插入成功,说明是第一次处理,执行业务逻辑;如果触发唯一键冲突,直接跳过。 - 进阶版可以用行锁:先执行
SELECT * FROM event_process WHERE event_id = 'xxx' FOR UPDATE,如果查到状态是未处理,就把状态改成处理中再执行逻辑,处理完更新为已完成。记得给处理中状态加超时时间,定期清理超时的记录,防止节点挂了导致事件被卡住。
2. 基于现有缓存的分布式锁
如果你的系统已经在用Redis这类缓存(大部分系统都会有),直接用它做分布式锁就行:
- 用Redis的原子命令
SET event_lock:{event_id} "processing" NX EX 30(NX是仅当key不存在时设置,EX是30秒过期,防止死锁)。如果命令返回成功,说明抢到了锁,处理事件;返回失败就跳过。 - 处理完事件后,用Lua脚本释放锁(避免误删别人的锁):
if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end - 注意:锁的过期时间要设置得比事件处理时间长一点,防止处理还没完成锁就过期了。
3. 共享存储文件锁(仅限特定场景)
如果你的节点挂载了共享存储(比如NFS、SAN),可以用文件锁实现简单的互斥:
- 处理事件前,尝试创建一个以
event-{event_id}.lock命名的文件,用排他锁打开(比如Linux下用flock命令,Java里用FileLockAPI)。如果能成功获取锁,就处理事件;拿不到锁说明已经有节点在处理了。 - 处理完成后释放锁并删除文件。这个方案成本低,但依赖共享存储的可靠性,适合小体量、对一致性要求不是极高的场景。
4. 基于服务注册中心的节点哈希路由
如果你的系统用了Eureka、Nacos这类注册中心,可以利用实例信息做路由:
- 把事件ID做哈希计算,比如
hash(event_id) % 在线节点数,然后把事件路由到对应的节点处理。只有匹配到的节点才执行处理逻辑,其他节点直接跳过。 - 进阶可以用一致性哈希算法,减少节点上下线时的哈希波动,避免大量事件重新路由。这个方案不用额外存储,完全基于现有注册中心,但依赖注册中心的实例信息实时性。
关键注意点
- 必须保证处理逻辑的幂等性:就算锁机制出了小问题,重复执行处理逻辑也不能产生错误结果(比如扣钱操作要先查订单状态,不能直接减余额)。
- 超时处理要到位:不管是数据库的
处理中状态,还是Redis锁的过期时间,都要设置合理的超时,防止节点挂了导致事件一直无法处理。
内容的提问来源于stack exchange,提问作者Bick
相关产品推荐
相关产品推荐

