项目刚开始,定时任务少,crontab -e 一行搞定。没人纠结------能跑就行。
后来任务多了。三个变十几个,散在不同机器上。没人知道哪个在跑、哪个停了、哪个重复了。要补跑、要暂停、要看日志,每次都先 ssh 上去找脚本在哪。然后你开始看调度框架:Airflow 能画 DAG,XXL-JOB 有 UI,K8s CronJob 跟着集群走。每个都像解法,每个都带来新问题。
选方案不是比谁先进,是想清楚你现在卡在哪一层。

代码内调度
还没到 crontab 之前,很多人先把任务写在代码里。
Python 有 schedule 和 APScheduler,Java 有 @Scheduled 和 Quartz。一条注解或几行代码,任务就定好了,不用出进程。
python
import schedule, time
schedule.every().day.at("03:00").do(backup)
while True:
schedule.run_pending()
time.sleep(1)
好处是跟着代码走。git 管理,版本可控,不会出现「服务器上有个 crontab 不知道谁写的」。调度逻辑和业务代码在同一个进程里,改业务顺带改调度,不散在系统层的 cron 里。
暗面:进程崩了全没。 你的调度器和业务代码在同一个进程里。进程崩了------定时任务也没了,不跑了,无声无息。crontab 是系统级的,进程崩了不影响它;代码内调度是进程级的,进程崩了它跟着死。

写 while True 挂后台,没日志,没监控,跑飞了你也不知道。更关键的是:你的 Web 服务是八实例部署,每个实例里都有一份调度代码,八份同时在跑。 防重复执行靠分布式锁------但锁本身又是个新问题。
一句话总结: 代码内调度跟着代码走,版本可控,git 管理。但进程崩了全没,多实例部署需要自己加锁。适用场景是任务和业务代码紧密耦合、单进程跑就行,不需要跨服务调度。
crontab
Linux 自带,一行表达式一条命令。不装任何东西,机器在它就跑。
0 3 * * * /data/scripts/backup.sh
暗面:不可见。 没有 UI,没有状态面板。看一个任务跑了没,得 grep CRON /var/log/syslog。任务多了,最大的问题不是执行失败,是你不知道它存不存在。
翻车现场:同事写了个 cron 做每日同步,离职半年后脚本还在跑,没人知道。磁盘被日志写满,服务崩了,排查的人翻 crontab 才发现------从头到尾没出过错。错的是没人知道它在。
还有时区:0 3 * * * 是凌晨三点,服务器跨时区部署了,这个三点是 UTC 的三点,比你本地晚八个小时。时区不在 cron 表达式里,在系统 TZ 里,没设就默认。
一句话总结: crontab 简单到极致------零依赖,系统自带,一行就够。但没有任何管理能力,任务一多你就不知道谁在跑。适用场景是任务数少、单机、不需要界面。
K8s CronJob
云原生版 crontab:YAML 定义,跟着集群调度。机器挂了 K8s 重新调度,日志跟着集群走,不用 ssh。
yaml
apiVersion: batch/v1
kind: CronJob
schedule: "0 3 * * *"
暗面:并发默认不拦。 concurrencyPolicy 默认 Allow------上一个 Pod 没跑完,下一个准时启动。两个 Pod 同时跑同一个任务,数据库里出重复数据。

更隐蔽的:它管启动时间,不管完成时间。上一个任务超时了,下一个照样到点起------两个任务在数据库里打架。startingDeadlineSeconds 和 concurrencyPolicy 是防这个的,但大部分人出问题才去看。
一句话总结: 有自愈能力、日志统一,比 crontab 强一档。但并发默认不拦,任务超时得自己管。适用场景是你已经在 K8s 里------不是为了 cron 上 K8s,是上了 K8s 就别额外搭调度平台。
Airflow
crontab 只管「什么时候启动」,Airflow 管「启动之后怎么走」。核心是 DAG:任务间有依赖,A 跑完跑 B,B 失败下游全停。
backup_db >> clean_logs >> send_report

crontab 里依赖靠时间差------「A 一点跑,B 一点半跑」,A 慢 B 就撞。Airflow 里依赖是代码,A 跑完 B 才启动,不猜时间。
暗面:重。 scheduler、webserver、worker、metadata DB 四个进程起跑,内存吃掉一个 G。二十个定时任务,每个跑几秒,调度器的内存是任务的好几倍。
翻车现场:团队上了 Airflow,DAG 图很漂亮,老板投屏好看。写了三十几个 DAG,每个就一两个 Task------没依赖,没分支,就是把小脚本包装成 DAG。有 DAG 的能力,没 DAG 的需求。
一句话总结: 依赖关系显式化,有 UI,失败重试和暂停都内置。但太重了,部署运维成本比任务本身还高。适用场景是任务间真的有依赖------DAG 图不是装饰。
XXL-JOB
国内最流行的轻量调度框架,Spring Boot 一套,自带 UI。和 crontab 比有界面,和 Airflow 比不要求你写 DAG。
核心是执行器 + 调度中心:调度中心管策略、路由、分片、重试,执行器管跑。之间 HTTP 通信。

crontab 查执行记录靠 grep。XXL-JOB 打开页面,调度记录、耗时、失败次数全在界面上。分片也实用:一个任务拆多片,十个执行器同时跑,单机二十分钟的事两分钟跑完。
暗面:概念多。 路由策略、分片广播、故障转移、阻塞处理策略------十几个概念,每个都要理解。你只是跑一个 curl,crontab 一行够,XXL-JOB 要配执行器、配路由策略、配阻塞策略------任务一行,配置十行。
翻车现场:分片广播配好了,一个执行器挂了。没报错------调度中心把分片匀给剩下的执行器,数据照样跑,少了一台,你没发现。路由策略选「故障转移」才对,但默认是「分片广播」。
一句话总结: 有 UI、有管理能力,分片和重试都内置,是 crontab 和 Airflow 之间的中间地带。但概念多,理解负担重,简单任务用它就是杀鸡用牛刀。适用场景是任务多到需要管理,团队有 Java 技术栈。
怎么选
没有最好。每个都有最擅长的战场,和最翻车的坑。
问自己三句话:
- 任务数有多少? 十以内------crontab,别为十个任务上调度中心。几十个、散在不同机器------上调度中心。
- 任务间有依赖吗? 没有------别上 Airflow。有------Airflow 或 XXL-JOB。
- 已经在什么生态里? 在 K8s------用 CronJob。不在------crontab 或 XXL-JOB,不为了 cron 上 K8s。

卡在规模,上调度中心。卡在依赖,上 Airflow。卡在轻量,退回去用 crontab------不是它好,是你还没复杂到需要更多。