真正可靠的方案是分布式锁与线程池分层协作:分布式锁控制批次全局准入,确保仅一个实例启动任务;线程池限制本机子任务并发数,防止资源耗尽。需对齐锁与事务生命周期、校验client id防误删、配置兜底与可观测性。

批处理场景下,多实例并发执行同一任务容易导致重复拉取、重复写库、数据覆盖等问题。单纯用线程池或单纯用分布式锁都不够——线程池只管本机调度,跨机器无效;分布式锁若不加内部并发控制,又会把整批任务串行化,吞吐暴跌。真正可靠的方案是让两者分层协作:分布式锁守入口,线程池控节奏。
分布式锁负责“谁可以启动这批任务”
每个批次用唯一 key(如 batch:20260610:user_export)加锁,确保同一时间仅有一个服务实例触发该批次。其他实例发现锁已被持有时,直接跳过或转入重试队列,避免重复调度、重复读取源数据、重复生成中间文件等撞车行为。
线程池负责“本实例内最多并发跑几个子任务”
拿到分布式锁后,不是一股脑提交全部子任务,而是交由本地线程池有序执行:
- 核心线程数建议设为 CPU 核心数 × 1.5~2,避免过度争抢 CPU
- 使用有界队列(如 LinkedBlockingQueue(100)),防止内存溢出
- 拒绝策略推荐 CallerRunsPolicy,让调用线程自己执行,便于快速感知压力并触发告警
- 每个子任务执行完毕后,必须确保释放资源(如关闭 DB 连接、归还 HTTP 客户端连接)
关键细节不能漏:锁生命周期与事务对齐
分布式锁的释放时机必须严格匹配业务事务边界:
- 错误做法:在事务提交前 unlock → 其他实例可能读到未提交中间状态,引发脏读或重复处理
- 正确做法:注册事务同步器,在 commit 或 rollback 后再释放锁,保证锁持有期覆盖整个事务生命周期
- 若子任务耗时长,需开启看门狗机制自动续期,避免锁提前过期被误抢
- 释放锁时必须校验 client ID,防止 A 实例误删 B 实例持有的锁
兜底与可观测性要跟上
生产环境不能只靠一层防护:
- 数据库层面加 SELECT FOR UPDATE 或唯一约束,作为分布式锁失效时的最后一道防线
- 记录锁获取/释放日志,标注批次 ID、实例 IP、耗时、是否超时,便于问题回溯
- 监控线程池活跃线程数、队列堆积量、锁竞争失败率,设置阈值自动告警
- 对长周期批处理,可引入断点续传机制,避免单次失败全量重跑
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











