用 Go 写了一个轻量级任务调度器,替换掉了笨重的 XXL-Job
Building a Lightweight Task Scheduler in Go to Replace XXL-Job
| Alex | 2026-08-24T09:24:48
XXL-Job 功能是强大,但是对于我们这种小团队来说太重了。花了一周时间用 Go 写了个轻量级的替代方案,核心代码不到 2000 行。
XXL-Job is powerful but overkill for small teams. Built a lightweight Go-based alternative in one week with under 2000 lines of core code.
我们团队之前用的 XXL-Job 做定时任务调度,功能确实很全,但是对于一个 5 人的小团队来说实在太重了。光是部署就需要一个独立的调度中心服务,还依赖 MySQL,运维成本不低。 为什么要自己写 其实市面上也有不少轻量级的方案,比如 robfig/cron、asynq 这些。但是我们的需求有点特殊: 需要支持多实例部署,同一个任务不能重复执行 需要有简单的 Web UI 看任务状态 需要支持任务依赖(A 执行完了才能执行 B) 失败重试和告警通知 看了一圈发现没有一个现成的库能完全满足,索性自己撸一个。 架构设计 整个调度器分三层: 调度层:基于 cron 表达式解析下次执行时间,用 Redis 分布式锁防止重复调度 执行层:每个任务是一个 Go 函数,通过 goroutine 并发执行,支持超时控制 存储层:任务定义和执行记录存 SQLite(对,就是 SQLite,小团队够用了) 核心实现 type Scheduler struct { tasks map[string]*Task lock distributed.Lock store Store notifier Notifier } func (s *Scheduler) Register(name string, spec string, fn TaskFunc, opts ...Option) { task := &Task{ Name: name, CronSpec: spec, Fn: fn, } for _, opt := range opts { opt(task) } s.tasks[name] = task } func (s *Scheduler) Run(ctx context.Context) error { ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case 分布式锁这块用的 Redis SETNX,锁的粒度是每个任务每次调度,key 格式是 scheduler:lock:{taskName}:{triggerTime}。这样即使多个实例同时触发,也只有一个能拿到锁。 效果 上线跑了一个月,稳定得很。部署也简单,就一个二进制文件加一个 SQLite 文件,Docker 镜像才 20MB。比起之前 XXL-Job 那一套,运维成本直接降了 80%。 当然这个方案不适合大规模场景,如果你们团队有几百个定时任务,还是老老实实用 XXL-Job 或者 Airflow 吧。
Our team was using XXL-Job for task scheduling. It's feature-rich, but way too heavy for a 5-person team. Deployment alone requires a dedicated scheduler service plus MySQL - significant ops overhead. Why Build Our Own There are lightweight alternatives like robfig/cron and asynq, but our requirements were specific: Multi-instance deployment with no duplicate task execution Simple Web UI for task status monitoring Task dependencies (B runs only after A completes) Failure retry and alert notifications No existing library fully covered these needs, so we built our own. Architecture Three layers: Scheduling Layer: Parses cron expressions for next execution time, uses Redis distributed locks to prevent duplicate scheduling Execution Layer: Each task is a Go function running in goroutines with timeout control Storage Layer: Task definitions and execution records stored in SQLite Results Been running stable for a month. Deployment is just one binary plus a SQLite file - Docker image is only 20MB. Ops cost dropped 80% compared to XXL-Job. Not suitable for large-scale scenarios with hundreds of tasks though.