在单体应用时代,写定时任务简直是送分题。在 Spring 里打个@Scheduled(cron = "0 0 2 * * ?"),每天凌晨两点,代码准时执行。
但当你把这套代码放到微服务集群里,部署了 10 台机器,灾难降临了:
到了凌晨 2 点,10 台机器同时触发定时任务,同一个用户的账户里被重复发了 10 张优惠券!老板连夜把你叫醒,财务让你赔钱。
为了解决“重复执行”的问题,架构师们开始了定时任务的演进之路。
🛑 一、中小厂的王者:XXL-JOB 与数据库锁的上限
既然不能让 10 台机器一起干,那就选出一个“倒霉蛋”去干。
这是目前业界最流行的分布式调度框架XXL-JOB的核心逻辑(在大多数常规场景下)。
XXL-JOB 有一个专门的调度中心。到了时间点,调度中心会去拉取你的集群列表,然后通过“轮询”或“一致性 Hash”策略,挑出其中一台机器,把任务发给它:“今天这 10 万个订单,就交给你来扫描了!”
致命瓶颈:单机的物理极限
如果数据量不是 10 万,而是1000 万呢?
被选中的那台机器看着 1000 万条数据陷入了沉思。它疯狂查库、处理,CPU 满载,跑了整整 2 个小时还没跑完。
而此时,集群里的另外 9 台机器在干嘛?它们在旁边喝茶看戏!CPU 占用率 0%。
这就是集中式调度最大的痛点:算力闲置,单机遭遇性能天花板。
⚔️ 二、算力大爆发:ElasticJob 与“分片”哲学
为了打破单机天花板,当当网开源了极其硬核的分布式调度框架:ElasticJob(现已捐赠给 Apache)。
它引入了一个在数据库领域非常熟悉的词:分片 (Sharding)。
既然 1 台机器干不完,那我就把 1000 万数据切成 10 份,让 10 台机器同时并发干!
1. 去中心化的架构:Zookeeper 坐镇
ElasticJob 极其激进地干掉了“中心化的调度服务端”。它只依赖Zookeeper (ZK)作为注册中心。
所有的业务机器启动时,都会向 ZK 报到。ZK 会记录下当前有几台机器在线。
2. 神奇的分片路由策略
假设我们将任务设置为10 个分片(分片项为0, 1, 2, 3, 4, 5, 6, 7, 8, 9)。
当前集群有3 台机器(A, B, C)。
ElasticJob 会通过 ZK 瞬间完成分配方案:
- 机器 A 分到:
0, 1, 2, 3 - 机器 B 分到:
4, 5, 6 - 机器 C 分到:
7, 8, 9
到了凌晨 2 点,3 台机器同时触发任务!
但它们在查数据库时,会带上自己的分片参数。
比如机器 A 的 SQL 是这样的:SELECT * FROM order WHERE status = '未支付' AND MOD(id, 10) IN (0, 1, 2, 3)。
震撼的结果:
1000 万条数据被完美切割,3 台机器同时狂飙,原本需要 2 小时的任务,现在只需要 20 分钟!如果老板嫌慢,你只需要再加 7 台机器,时间还能再缩短几倍!
🛡️ 三、高可用极致拉扯:机器挂了怎么办?
大厂架构师永远会问一个问题:“如果跑着跑着,机器挂了怎么办?”
在 ElasticJob 的世界里,Zookeeper 扮演了极其冷酷的监工角色。
场景推演:
如果机器 B(拿着分片 4, 5, 6)突然断电宕机了。
- 心跳丢失:ZK 瞬间发现机器 B 与自己断开了临时节点连接。
- 触发重新分片 (Resharding):ZK 立刻在集群里广播:“兄弟们先停一下,B 阵亡了,我们重新分家产!”
- 瞬间接管:剩下的机器 A 和 C 重新分配这 10 个分片。
- 机器 A 分到:
0, 1, 2, 3, 4 - 机器 C 分到:
5, 6, 7, 8, 9
- 机器 A 分到:
- 继续执行:A 和 C 带着新的分片参数,无缝接管了机器 B 没干完的活,继续执行。
这就是 ElasticJob 最迷人的地方:极致的弹性扩缩容与故障转移 (Failover)。
🎯 四、总结:技术选型的权衡之道
在真实的业务战场上,没有绝对的碾压,只有最合适的选择。
- XXL-JOB (实用主义者):自带 Web UI 管理后台,轻量级,支持动态改 Cron 表达式,能满足 90% 互联网公司的日常需求。虽然它也支持分片广播,但底层重度依赖数据库锁。
- ElasticJob (重型装甲车):重度依赖 Zookeeper,缺少花里胡哨的管理后台,但它的去中心化设计和极致的分片模型,是处理金融级、海量级批处理任务的绝对王者。
一句话总结:当你只面对几十万数据时,请用 XXL-JOB 享受开发的便捷;但当你的定时任务每次要扫过亿行表、甚至要把任务分发给 100 台机器并行狂奔时,ElasticJob 的分片魔法才是你唯一的救命稻草。