多实例批量脚本如何避免重复处理相同记录?
可选的分布式批处理防重复方案
以下方案均经过生产环境验证,可适配不同的技术栈和业务场景:
1. 乐观锁+状态机+行锁优化(对原标记instance_id方案的升级)
给待处理记录的表新增三个字段:
processing_status:枚举值,分为待处理/处理中/处理完成/处理失败update_time:状态更新时间戳instance_id:处理当前记录的实例ID
实例拉取任务时直接执行带条件的更新语句(以MySQL 8.0+为例):
UPDATE 待处理表 SET processing_status = '处理中', update_time = NOW(), instance_id = '当前实例唯一ID' WHERE id IN ( SELECT id FROM 待处理表 WHERE processing_status = '待处理' LIMIT 100 -- 每次拉取的批量大小,可按需调整 FOR UPDATE SKIP LOCKED -- 跳过已经被其他实例锁定的行,避免阻塞和重复抢 ) RETURNING *; -- 返回当前实例抢到的所有记录
额外加超时重置逻辑:单独开一个定时任务,把处理中状态且update_time超过合理阈值(比如单条记录最长处理时间的2倍)的记录重置为待处理,避免实例意外挂掉导致任务积压。
优点:无需引入额外组件,修改成本低,性能足以支撑千万级记录的处理
2. 分布式消息队列解耦
把所有待处理记录的唯一ID提前写入Kafka/RabbitMQ这类支持消息单消费的队列中,所有批处理实例作为队列的消费者组成员拉取消息:
- 开启队列的手动ACK机制,实例只有成功处理完对应记录后,才给队列返回消费成功的确认
- 若实例处理过程中挂掉,未返回ACK的消息会在超时后被队列重新投递给其他正常运行的实例
优点:完全不需要修改业务库表结构,扩展性极强,新增实例直接启动消费者即可,无需调整分配逻辑
3. 分布式锁分片(对原预分配范围方案的升级)
无需单独的管理节点做分配,用Redis等缓存实现分布式锁:
- 先把所有待处理记录按ID哈希分成N个分片(比如N设为实例数的2~3倍)
- 每个实例启动后循环尝试抢未被占用的分片锁,抢到对应分片的锁后,就处理该分片下的所有记录
- 分片锁设置合理的超时时间,实例挂掉后锁会自动释放,其他实例可以抢到该分片继续处理
优点:避免预分配方案中单个实例故障导致对应分片任务完全卡住的问题,调度逻辑轻量
4. 幂等兜底逻辑
不管用以上哪种分配方案,都建议给处理逻辑增加幂等校验:处理每条记录前先判断该记录是否已经被标记为处理完成,如果是就直接跳过,就算极端场景下出现重复分配的情况,也不会产生脏数据。
你之前用到的两个方案的可优化点:
- 预分配范围方案:可以增加故障检测逻辑,发现某实例超时未上报进度时,把它负责的分片重新分配给其他实例
- 直接标记instance_id的方案:如果不加行锁控制,多个实例可能同时读到instance_id为null的同一条记录,导致重复处理,加上乐观锁和SKIP LOCKED逻辑即可解决
内容的提问来源于stack exchange,提问作者Kannan K
相关产品推荐
相关产品推荐

