如何设计一个简单易用的定时任务模块
项目地址
- 后端项目 :github.com/qxkjsoft/gi...
- 前端项目 :github.com/qxkjsoft/gi...
目录
- 前言
- 一、整体架构设计
- [1.1 核心组件](#1.1 核心组件 "#11-%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6")
- [1.2 核心接口定义](#1.2 核心接口定义 "#12-%E6%A0%B8%E5%BF%83%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89")
- 二、任务模型设计
- [2.1 任务结构](#2.1 任务结构 "#21-%E4%BB%BB%E5%8A%A1%E7%BB%93%E6%9E%84")
- [2.2 执行策略](#2.2 执行策略 "#22-%E6%89%A7%E8%A1%8C%E7%AD%96%E7%95%A5")
- [2.3 阻塞策略](#2.3 阻塞策略 "#23-%E9%98%BB%E5%A1%9E%E7%AD%96%E7%95%A5")
- 三、执行器设计
- [3.1 执行器接口](#3.1 执行器接口 "#31-%E6%89%A7%E8%A1%8C%E5%99%A8%E6%8E%A5%E5%8F%A3")
- [3.2 自定义执行器示例](#3.2 自定义执行器示例 "#32-%E8%87%AA%E5%AE%9A%E4%B9%89%E6%89%A7%E8%A1%8C%E5%99%A8%E7%A4%BA%E4%BE%8B")
- [3.3 执行器注册](#3.3 执行器注册 "#33-%E6%89%A7%E8%A1%8C%E5%99%A8%E6%B3%A8%E5%86%8C")
- 四、任务生命周期管理
- [4.1 任务加载](#4.1 任务加载 "#41-%E4%BB%BB%E5%8A%A1%E5%8A%A0%E8%BD%BD")
- [4.2 任务执行流程](#4.2 任务执行流程 "#42-%E4%BB%BB%E5%8A%A1%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B")
- [4.3 结果处理](#4.3 结果处理 "#43-%E7%BB%93%E6%9E%9C%E5%A4%84%E7%90%86")
- 五、API接口设计
- [5.1 任务管理接口](#5.1 任务管理接口 "#51-%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E6%8E%A5%E5%8F%A3")
- [5.2 执行结果接口](#5.2 执行结果接口 "#52-%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C%E6%8E%A5%E5%8F%A3")
- [5.3 创建任务示例](#5.3 创建任务示例 "#53-%E5%88%9B%E5%BB%BA%E4%BB%BB%E5%8A%A1%E7%A4%BA%E4%BE%8B")
- 六、数据库设计
- [6.1 任务表(sys_jobs)](#6.1 任务表(sys_jobs) "#61-%E4%BB%BB%E5%8A%A1%E8%A1%A8sys_jobs")
- [6.2 执行结果表(sys_job_results)](#6.2 执行结果表(sys_job_results) "#62-%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C%E8%A1%A8sys_job_results")
- 七、高级特性
- [7.1 超时控制](#7.1 超时控制 "#71-%E8%B6%85%E6%97%B6%E6%8E%A7%E5%88%B6")
- [7.2 重试机制](#7.2 重试机制 "#72-%E9%87%8D%E8%AF%95%E6%9C%BA%E5%88%B6")
- [7.3 并行控制](#7.3 并行控制 "#73-%E5%B9%B6%E8%A1%8C%E6%8E%A7%E5%88%B6")
- 八、最佳实践
- [8.1 执行器设计原则](#8.1 执行器设计原则 "#81-%E6%89%A7%E8%A1%8C%E5%99%A8%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99")
- [8.2 任务配置建议](#8.2 任务配置建议 "#82-%E4%BB%BB%E5%8A%A1%E9%85%8D%E7%BD%AE%E5%BB%BA%E8%AE%AE")
- [8.3 监控告警](#8.3 监控告警 "#83-%E7%9B%91%E6%8E%A7%E5%91%8A%E8%AD%A6")
- 九、扩展性设计
- [9.1 插件化支持](#9.1 插件化支持 "#91-%E6%8F%92%E4%BB%B6%E5%8C%96%E6%94%AF%E6%8C%81")
- [9.2 动态注册](#9.2 动态注册 "#92-%E5%8A%A8%E6%80%81%E6%B3%A8%E5%86%8C")
- 十、前端设计与使用
- [10.1 前端页面结构](#10.1 前端页面结构 "#101-%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84")
- [10.2 任务管理页面功能](#10.2 任务管理页面功能 "#102-%E4%BB%BB%E5%8A%A1%E7%AE%A1%E7%90%86%E9%A1%B5%E9%9D%A2%E5%8A%9F%E8%83%BD")
- [10.3 任务创建/编辑表单](#10.3 任务创建/编辑表单 "#103-%E4%BB%BB%E5%8A%A1%E5%88%9B%E5%BB%BA%E7%BC%96%E8%BE%91%E8%A1%A8%E5%8D%95")
- [10.4 执行日志页面](#10.4 执行日志页面 "#104-%E6%89%A7%E8%A1%8C%E6%97%A5%E5%BF%97%E9%A1%B5%E9%9D%A2")
- [10.5 前端API调用示例](#10.5 前端API调用示例 "#105-%E5%89%8D%E7%AB%AFapi%E8%B0%83%E7%94%A8%E7%A4%BA%E4%BE%8B")
- 十一、后端核心代码解读
- [11.1 调度器核心实现](#11.1 调度器核心实现 "#111-%E8%B0%83%E5%BA%A6%E5%99%A8%E6%A0%B8%E5%BF%83%E5%AE%9E%E7%8E%B0")
- [11.2 结果处理器实现](#11.2 结果处理器实现 "#112-%E7%BB%93%E6%9E%9C%E5%A4%84%E7%90%86%E5%99%A8%E5%AE%9E%E7%8E%B0")
- [11.3 服务层设计](#11.3 服务层设计 "#113-%E6%9C%8D%E5%8A%A1%E5%B1%82%E8%AE%BE%E8%AE%A1")
- [11.4 参数验证设计](#11.4 参数验证设计 "#114-%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81%E8%AE%BE%E8%AE%A1")
- 十二、实际应用场景与案例
- [12.1 典型应用场景](#12.1 典型应用场景 "#121-%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF")
- [12.2 性能优化案例](#12.2 性能优化案例 "#122-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%A1%88%E4%BE%8B")
- [12.3 故障处理案例](#12.3 故障处理案例 "#123-%E6%95%85%E9%9A%9C%E5%A4%84%E7%90%86%E6%A1%88%E4%BE%8B")
- 十三、总结与展望
- [13.1 设计总结](#13.1 设计总结 "#131-%E8%AE%BE%E8%AE%A1%E6%80%BB%E7%BB%93")
- [13.2 技术亮点](#13.2 技术亮点 "#132-%E6%8A%80%E6%9C%AF%E4%BA%AE%E7%82%B9")
- [13.3 未来展望](#13.3 未来展望 "#133-%E6%9C%AA%E6%9D%A5%E5%B1%95%E6%9C%9B")
- [13.4 适用场景](#13.4 适用场景 "#134-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF")
- 参考代码
前言
在日常开发中,我们经常遇到需要定时执行某些任务的场景,比如数据同步、报表生成、缓存清理、消息推送等。一个好的定时任务模块应该具备以下特点:
- 简单易用:配置直观,上手快
- 灵活可扩展:支持自定义任务类型
- 可靠稳定:支持重试、超时控制
- 可观测性:记录执行日志和结果
本文将基于一个实际项目中的定时任务模块设计,分享如何从零开始构建一个简单易用的定时任务系统。
一、整体架构设计
1.1 核心组件
我们的定时任务模块主要由以下几个核心组件构成:
scss
┌─────────────────────────────────────────────────────────────┐
│ API Layer │
│ (Controller: SysJobsController, SysJobResultsController) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service Layer │
│ (SysJobsService, SysJobResultsService) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Scheduler Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Executor │ │ Job Manager │ │Result Handler│ │
│ │ Registry │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Storage Layer │
│ (sys_jobs, sys_job_results) │
└─────────────────────────────────────────────────────────────┘
1.2 核心接口定义
首先,我们定义了调度器的核心接口 JobSchedulerInterf:
go
type JobSchedulerInterf interface {
// 生命周期管理
Start()
Stop()
// 执行器管理
RegisterExecutor(executor schedulerhelper.Executor)
ListExecutors() []schedulerhelper.Executor
// 任务管理
AddOrUpdateJob(job *schedulerhelper.Job) (string, error)
EnableJob(jobID string) error
DisableJob(jobID string) error
DeleteJob(jobID string) error
ExecuteNow(jobID string) error
ListJobs() []*schedulerhelper.Job
JobExists(jobID string) bool
// 结果获取
GetResults() <-chan *schedulerhelper.JobResult
}
这个接口清晰地定义了调度器应该具备的所有能力,包括生命周期管理、执行器管理、任务管理和结果获取。
二、任务模型设计
2.1 任务结构
一个完整的任务定义包含以下关键信息:
go
type Job struct {
ID string // 任务唯一标识
Group string // 任务分组
Name string // 任务名称
Description string // 任务描述
ExecutorName string // 执行器名称
ExecutionPolicy ExecutionPolicy // 执行策略:单次/重复
Status JobStatus // 状态:启用/禁用
CronExpression string // Cron表达式
Parameters map[string]interface{} // 任务参数
BlockingPolicy BlockingPolicy // 阻塞策略:丢弃/并行
Timeout time.Duration // 超时时间
MaxRetry int // 最大重试次数
RetryInterval time.Duration // 重试间隔
ParallelNum int // 并行数
RunningCount int // 当前运行数
}
2.2 执行策略
我们支持两种执行策略:
- PolicyOnce(单次执行):任务执行成功后自动禁用
- PolicyRepeat(重复执行):按照Cron表达式周期性执行
2.3 阻塞策略
当任务下一次执行时间到达,但上一次任务还在运行时,我们支持两种处理策略:
- BlockDiscard(丢弃):跳过本次执行
- BlockParallel(并行):允许并行执行(可配置最大并行数)
三、执行器设计
3.1 执行器接口
执行器是任务的实际执行单元,通过接口实现解耦:
go
type Executor interface {
Execute(ctx context.Context, job *Job) error
Name() string
}
3.2 自定义执行器示例
实现一个自定义执行器非常简单,只需实现 Executor 接口:
go
type DemoExecutor struct{}
func (e *DemoExecutor) Execute(ctx context.Context, job *schedulerhelper.Job) error {
log.Printf("Executor %s executing job %s with params: %v",
e.Name(), job.Name, job.Parameters)
// 模拟任务执行
select {
case <-time.After(2 * time.Second):
log.Printf("Job %s completed successfully", job.Name)
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (e *DemoExecutor) Name() string {
return "demo-executor"
}
3.3 执行器注册
在应用启动时,通过 RegisterExecutors 函数注册所有执行器:
go
func RegisterExecutors() {
app.JobScheduler.RegisterExecutor(&executors.DemoExecutor{})
// 添加更多执行器...
}
四、任务生命周期管理
4.1 任务加载
应用启动时,从数据库加载所有启用的任务:
go
func LoadJobsFromDB() {
ctx := context.Background()
// 查询所有启用的任务
var jobsList models.SysJobsList
err := app.DB().WithContext(ctx).
Model(&models.SysJobs{}).
Where("status = ?", 1).
Find(&jobsList).Error
// 遍历任务列表,添加到调度器
for _, job := range jobsList {
// 解析参数、构建Job对象、添加到调度器
app.JobScheduler.AddOrUpdateJob(schedulerJob)
}
// 启动结果处理器
StartResultHandler()
}
4.2 任务执行流程
sql
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cron Trigger│───▶│ Job Executor│───▶│Result Handler│
└─────────────┘ └─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Executor │ │ Database │
│ Interface │ │ Storage │
└─────────────┘ └─────────────┘
4.3 结果处理
任务执行结果通过通道异步处理,避免阻塞调度器:
go
func StartResultHandler() {
go func() {
resultsChan := app.JobScheduler.GetResults()
for result := range resultsChan {
// 保存结果到数据库
saveJobResult(result)
// 单次执行任务成功后自动禁用
if result.ExecutionPolicy == schedulerhelper.PolicyOnce
&& result.Status == "SUCCESS" {
disableJob(result.JobID)
}
}
}()
}
五、API接口设计
5.1 任务管理接口
| 接口 | 方法 | 说明 |
|---|---|---|
/api/sysJobs/list |
GET | 获取任务列表(分页) |
/api/sysJobs/:id |
GET | 获取任务详情 |
/api/sysJobs/add |
POST | 创建任务 |
/api/sysJobs/edit |
PUT | 更新任务 |
/api/sysJobs/delete |
DELETE | 删除任务 |
/api/sysJobs/setStatus |
PUT | 设置任务状态 |
/api/sysJobs/executeNow |
POST | 立即执行任务 |
/api/sysJobs/executors |
GET | 获取执行器列表 |
5.2 执行结果接口
| 接口 | 方法 | 说明 |
|---|---|---|
/api/sysJobResults/list |
GET | 获取执行结果列表(分页) |
/api/sysJobResults/:id |
GET | 获取执行结果详情 |
/api/sysJobResults/delete |
DELETE | 删除执行结果 |
5.3 创建任务示例
json
POST /api/sysJobs/add
{
"group": "data-sync",
"name": "用户数据同步",
"description": "每天凌晨2点同步用户数据",
"executorName": "user-sync-executor",
"executionPolicy": 1,
"status": 1,
"cronExpression": "0 0 2 * * *",
"parameters": "{\"batchSize\": 1000}",
"blockingPolicy": 0,
"timeout": 300000000000,
"maxRetry": 3,
"retryInterval": 60000000000,
"parallelNum": 1
}
六、数据库设计
6.1 任务表(sys_jobs)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 任务ID(主键) |
| group | string | 任务分组 |
| name | string | 任务名称 |
| description | string | 任务描述 |
| executor_name | string | 执行器名称 |
| execution_policy | int | 执行策略(0:单次 1:重复) |
| status | int | 状态(0:禁用 1:启用) |
| cron_expression | string | Cron表达式 |
| parameters | string | 任务参数(JSON) |
| blocking_policy | int | 阻塞策略(0:丢弃 1:并行) |
| timeout | bigint | 超时时间(纳秒) |
| max_retry | int | 最大重试次数 |
| retry_interval | bigint | 重试间隔(纳秒) |
| parallel_num | int | 并行数 |
6.2 执行结果表(sys_job_results)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 结果ID(主键) |
| job_id | string | 任务ID |
| status | string | 执行状态 |
| start_time | timestamp | 开始时间 |
| end_time | timestamp | 结束时间 |
| duration | bigint | 执行时长(纳秒) |
| retry_count | int | 重试次数 |
| error | text | 错误信息 |
| created_at | timestamp | 创建时间 |
七、高级特性
7.1 超时控制
每个任务可以设置超时时间,防止任务长时间阻塞:
go
ctx, cancel := context.WithTimeout(context.Background(), job.Timeout)
defer cancel()
err := executor.Execute(ctx, job)
7.2 重试机制
任务执行失败时,支持自动重试:
go
for i := 0; i <= job.MaxRetry; i++ {
err := executor.Execute(ctx, job)
if err == nil {
break
}
if i < job.MaxRetry {
time.Sleep(job.RetryInterval)
}
}
7.3 并行控制
通过 RunningCount 和 ParallelNum 控制任务并行执行数量:
go
if job.BlockingPolicy == BlockParallel {
if job.RunningCount < job.ParallelNum {
// 允许并行执行
job.RunningCount++
} else {
// 达到并行上限,跳过本次执行
return
}
}
八、最佳实践
8.1 执行器设计原则
- 幂等性:执行器应该是幂等的,多次执行结果一致
- 超时处理:正确处理 context 的取消信号
- 错误处理:返回明确的错误信息
- 日志记录:记录关键步骤和异常信息
8.2 任务配置建议
- 合理设置超时:根据任务实际执行时间设置
- 适当重试:网络类任务可以重试,数据类任务谨慎重试
- 阻塞策略:短任务可并行,长任务建议丢弃
- 参数校验:在创建任务时校验参数有效性
8.3 监控告警
- 定期检查任务执行结果表
- 对失败任务设置告警
- 监控任务执行时长趋势
九、扩展性设计
9.1 插件化支持
系统支持通过插件方式扩展执行器,如示例插件:
go
// plugins/example/scheduler/executors/example_executor.go
type ExampleExecutor struct{}
func (e *ExampleExecutor) Execute(ctx context.Context, job *schedulerhelper.Job) error {
// 自定义执行逻辑
return nil
}
func (e *ExampleExecutor) Name() string {
return "example-executor"
}
9.2 动态注册
插件可以在加载时动态注册执行器:
go
// plugins/example/scheduler/register.go
func RegisterExecutors() {
app.JobScheduler.RegisterExecutor(&executors.ExampleExecutor{})
}
十、前端设计与使用
10.1 前端页面结构
根据系统菜单配置,定时任务模块包含两个主要页面:
| 菜单ID | 路由 | 组件 | 标题 | 图标 |
|---|---|---|---|---|
| 140341 | /sysjobs | Sysjobs | sysjobs | functions |
| 140342 | /system/sysjobslist | system/sysjobs/sysjobslist | jobslist | IconList |
| 140347 | /system/joblog | system/sysjobresults/sysjobresultslist | joblog | IconHistory |
11.2 任务管理页面功能
任务列表页面 (system/sysjobs/sysjobslist) 提供以下功能:
-
任务列表展示
- 分页展示所有定时任务
- 显示任务状态(启用/禁用)
- 显示下次执行时间
- 显示当前运行中的任务数
-
任务操作
- 新增任务:点击"新增"按钮打开创建表单
- 编辑任务:点击任务行进入编辑模式
- 删除任务:支持单个删除
- 启用/禁用:切换任务状态
- 立即执行:手动触发任务执行
-
筛选功能
- 按任务分组筛选
- 按任务名称搜索
- 按执行器名称筛选
- 按状态筛选
11.3 任务创建/编辑表单
创建或编辑任务时,需要填写以下信息:
javascript
// 任务表单数据结构
{
group: "data-sync", // 任务分组(必填)
name: "用户数据同步", // 任务名称(必填)
description: "每天凌晨2点同步用户数据",
executorName: "user-sync-executor", // 执行器名称(必填,从下拉列表选择)
executionPolicy: 1, // 执行策略:0-单次执行,1-重复执行
status: 1, // 状态:0-禁用,1-启用
cronExpression: "0 0 2 * * *", // Cron表达式(必填)
parameters: '{"batchSize": 1000}', // 任务参数(JSON格式)
blockingPolicy: 0, // 阻塞策略:0-丢弃,1-并行
timeout: 300000000000, // 超时时间(纳秒)
maxRetry: 3, // 最大重试次数
retryInterval: 60000000000, // 重试间隔(纳秒)
parallelNum: 1 // 并行数
}
11.4 执行日志页面
执行日志页面 (system/sysjobresults/sysjobresultslist) 提供以下功能:
-
日志列表展示
- 分页展示任务执行记录
- 显示执行状态(成功/失败)
- 显示执行时长
- 显示重试次数
- 显示错误信息(如有)
-
日志操作
- 查看详情:点击日志行查看完整执行信息
- 删除日志:支持单个删除
-
筛选功能
- 按任务ID筛选
- 按执行状态筛选
- 按时间范围筛选
11.5 前端API调用示例
javascript
// 获取任务列表
async function getJobList(params) {
const response = await axios.get('/api/sysJobs/list', { params });
return response.data;
}
// 创建任务
async function createJob(data) {
const response = await axios.post('/api/sysJobs/add', data);
return response.data;
}
// 更新任务
async function updateJob(data) {
const response = await axios.put('/api/sysJobs/edit', data);
return response.data;
}
// 删除任务
async function deleteJob(id) {
const response = await axios.delete('/api/sysJobs/delete', {
params: { id }
});
return response.data;
}
// 设置任务状态
async function setJobStatus(id, status) {
const response = await axios.put('/api/sysJobs/setStatus', null, {
params: { id, status }
});
return response.data;
}
// 立即执行任务
async function executeJobNow(id) {
const response = await axios.post('/api/sysJobs/executeNow', null, {
params: { id }
});
return response.data;
}
// 获取执行器列表
async function getExecutors() {
const response = await axios.get('/api/sysJobs/executors');
return response.data;
}
// 获取执行结果列表
async function getJobResults(params) {
const response = await axios.get('/api/sysJobResults/list', { params });
return response.data;
}
十一、后端核心代码解读
11.1 调度器核心实现
调度器是整个定时任务模块的核心,基于 robfig/cron 库实现。
12.1.1 调度器结构
go
type JobScheduler struct {
mu sync.RWMutex // 读写锁,保护并发访问
cron *cron.Cron // cron调度器
jobs map[string]*Job // 任务存储
executors map[string]Executor // 执行器存储
jobResults chan *JobResult // 任务结果通道
logger JobLogger // 日志记录器
wg sync.WaitGroup // 等待正在执行的任务完成
}
设计要点:
- 使用
sync.RWMutex保护共享数据,支持高并发读写 - 使用通道异步传递执行结果,避免阻塞调度器
- 使用
sync.WaitGroup确保优雅关闭时所有任务完成
12.1.2 任务添加流程
go
func (s *JobScheduler) AddOrUpdateJob(job *Job) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 1. 判断是新增还是更新
action := "更新"
if _, exists := s.jobs[job.ID]; !exists {
action = "创建"
if job.ID == "" {
job.ID = generateJobID() // 自动生成ID
}
job.CreatedAt = time.Now()
}
// 2. 验证任务配置
if err := validateJob(job); err != nil {
return "", err
}
// 3. 如果任务已存在且已调度,先移除
if existingJob, exists := s.jobs[job.ID]; exists && existingJob.cronEntryID != 0 {
s.cron.Remove(existingJob.cronEntryID)
}
// 4. 如果任务启用状态,添加到cron调度
if job.Status == StatusEnabled {
entryID, err := s.cron.AddFunc(job.CronExpression, s.createJobFunc(job))
if err != nil {
return "", fmt.Errorf("failed to add job to cron: %v", err)
}
job.cronEntryID = entryID
}
// 5. 保存到内存
s.jobs[job.ID] = job
s.logger.LogJobLifecycle(job, action)
return job.ID, nil
}
关键设计:
- 支持任务ID自动生成
- 更新任务时先移除旧的调度,再添加新的调度
- 只有启用状态的任务才会被添加到cron调度器
12.1.3 任务执行流程
go
func (s *JobScheduler) executeJob(job *Job) {
s.wg.Add(1)
defer s.wg.Done()
startTime := time.Now()
jobExecutionID := fmt.Sprintf("%s-%d", job.ID, startTime.UnixNano())
// 重试循环
for currentRetry := 0; currentRetry <= job.MaxRetry; currentRetry++ {
result := &JobResult{
JobID: job.ID,
StartTime: time.Now(),
RetryCount: currentRetry,
ExecutionPolicy: job.ExecutionPolicy,
}
// 1. 获取执行器
s.mu.RLock()
executor, exists := s.executors[job.ExecutorName]
s.mu.RUnlock()
if !exists {
result.Status = "FAILED"
result.Error = fmt.Errorf("executor not found: %s", job.ExecutorName)
s.sendResult(result)
return
}
// 2. 创建带超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), job.Timeout)
// 3. 执行任务(传递深拷贝,避免并发修改)
jobCopy := job.Clone()
err = executor.Execute(ctx, jobCopy)
cancel()
result.EndTime = time.Now()
result.Duration = result.EndTime.Sub(result.StartTime)
// 4. 处理执行结果
if err != nil {
result.Status = "FAILED"
result.Error = err
if currentRetry < job.MaxRetry {
time.Sleep(job.RetryInterval)
continue // 继续重试
} else {
s.sendResult(result)
return // 达到最大重试次数
}
} else {
result.Status = "SUCCESS"
s.sendResult(result)
// 5. 单次执行策略:执行成功后自动禁用
if job.ExecutionPolicy == PolicyOnce {
s.DisableJob(job.ID)
}
return
}
}
}
关键设计:
- 使用
context.WithTimeout实现超时控制 - 使用
job.Clone()创建深拷贝,避免并发修改问题 - 支持失败重试,重试间隔可配置
- 单次执行任务成功后自动禁用
12.1.4 阻塞策略实现
go
func (s *JobScheduler) canExecute(job *Job) bool {
s.mu.RLock()
defer s.mu.RUnlock()
currentJob, exists := s.jobs[job.ID]
if !exists {
return false
}
runningCount := currentJob.RunningCount
switch job.BlockingPolicy {
case BlockDiscard:
// 丢弃策略:同一时间只能有一个任务执行
if runningCount == 0 {
return true
}
return false
case BlockParallel:
// 并行策略:允许指定数量的任务并行执行
if job.ParallelNum <= 0 {
return true // 无限制
}
if runningCount < job.ParallelNum {
return true
}
return false
default:
// 默认策略与 BlockDiscard 相同
return runningCount == 0
}
}
关键设计:
BlockDiscard:确保任务串行执行,避免资源竞争BlockParallel:支持并行执行,提高吞吐量- 通过
RunningCount实时跟踪运行中的任务数
12.2 结果处理器实现
结果处理器是一个后台协程,负责将任务执行结果持久化到数据库。
go
func StartResultHandler() {
ctx, cancel := context.WithCancel(context.Background())
resultHandlerCancel = cancel
go func() {
resultsChan := app.JobScheduler.GetResults()
for {
select {
case <-ctx.Done():
// 处理剩余的结果
drainResults(resultsChan)
return
case result, ok := <-resultsChan:
if !ok {
return // 通道已关闭
}
// 保存结果到数据库
if err := saveJobResult(result); err != nil {
app.ZapLog.Error("保存任务结果失败",
zap.String("jobID", result.JobID),
zap.Error(err))
}
}
}
}()
}
关键设计:
- 使用
context.Context支持优雅关闭 - 通道关闭时处理剩余结果,确保数据不丢失
- 异步处理,不阻塞调度器
11.3 服务层设计
服务层负责业务逻辑处理,是控制器和调度器之间的桥梁。
11.3.1 创建任务流程
go
func (s *SysJobsService) Create(c *gin.Context, req models.SysJobsCreateRequest) (*models.SysJobs, error) {
// 1. 验证Cron表达式
if err := schedulerhelper.ValidateCronExpression(req.CronExpression); err != nil {
return nil, errors.New("Cron表达式格式错误: " + err.Error())
}
// 2. 解析任务参数JSON字符串
var parameters map[string]interface{}
if req.Parameters != "" {
if err := json.Unmarshal([]byte(req.Parameters), ¶meters); err != nil {
return nil, errors.New("任务参数JSON格式错误: " + err.Error())
}
}
// 3. 构建调度器Job对象
job := &schedulerhelper.Job{
Group: req.Group,
Name: req.Name,
Description: req.Description,
ExecutorName: req.ExecutorName,
ExecutionPolicy: schedulerhelper.ExecutionPolicy(req.ExecutionPolicy),
Status: schedulerhelper.JobStatus(req.Status),
CronExpression: req.CronExpression,
Parameters: parameters,
BlockingPolicy: schedulerhelper.BlockingPolicy(req.BlockingPolicy),
Timeout: time.Duration(req.Timeout),
MaxRetry: req.MaxRetry,
RetryInterval: time.Duration(req.RetryInterval),
ParallelNum: req.ParallelNum,
}
// 4. 调用调度器AddOrUpdateJob生成jobID
jobID, err := app.JobScheduler.AddOrUpdateJob(job)
if err != nil {
return nil, errors.New("添加任务到调度器失败: " + err.Error())
}
// 5. 创建sys_jobs记录
sysJobs := models.NewSysJobs()
sysJobs.Id = jobID
// ... 设置其他字段
// 6. 保存到数据库
if err := sysJobs.Create(c); err != nil {
return nil, err
}
return sysJobs, nil
}
关键设计:
- 先验证参数,再调用调度器
- 调度器和数据库双重存储,确保数据一致性
- 错误信息清晰,便于调试
11.4 参数验证设计
使用结构体标签实现参数验证:
go
type SysJobsCreateRequest struct {
Validator
Group string `form:"group" validate:"required" message:"任务分组名称不能为空"`
Name string `form:"name" validate:"required" message:"任务名称不能为空"`
ExecutorName string `form:"executorName" validate:"required" message:"执行器名称不能为空"`
CronExpression string `form:"cronExpression" validate:"required" message:"Cron表达式不能为空"`
// ... 其他字段
}
关键设计:
- 使用
validate标签定义验证规则 - 使用
message标签自定义错误消息 - 统一的验证接口,便于扩展
十二、实际应用场景与案例
12.1 典型应用场景
12.1.1 数据同步任务
- 场景:每天凌晨2点同步用户数据到数据仓库
- 配置 :
- 执行器:
user-sync-executor - Cron表达式:
0 0 2 * * * - 参数:
{"batchSize": 1000, "source": "mysql", "target": "data_warehouse"} - 超时时间:30分钟
- 重试次数:3次
- 执行器:
12.1.2 报表生成任务
- 场景:每周一上午8点生成周报
- 配置 :
- 执行器:
report-generator-executor - Cron表达式:
0 0 8 * * 1 - 参数:
{"reportType": "weekly", "recipients": ["manager@company.com"]} - 执行策略:重复执行
- 执行器:
12.1.3 缓存清理任务
- 场景:每小时清理过期的缓存数据
- 配置 :
- 执行器:
cache-cleaner-executor - Cron表达式:
0 0 * * * * - 参数:
{"cacheType": "redis", "ttl": 3600} - 阻塞策略:并行执行(允许多个实例同时运行)
- 执行器:
12.2 性能优化案例
12.2.1 高并发场景优化
- 问题:大量任务同时触发导致系统负载过高
- 解决方案 :
- 使用
BlockParallel策略限制并行数 - 调整任务的执行时间,错峰执行
- 增加任务超时时间,避免长时间阻塞
- 使用
12.2.2 大数据量任务优化
- 问题:数据同步任务处理大量数据时内存占用过高
- 解决方案 :
- 使用分批次处理,设置合理的
batchSize - 增加任务超时时间
- 监控任务执行时长,及时调整参数
- 使用分批次处理,设置合理的
12.3 故障处理案例
12.3.1 网络异常处理
- 场景:外部API调用失败
- 处理方案 :
- 配置合理的重试次数和重试间隔
- 在任务参数中记录失败原因
- 设置告警机制,及时通知管理员
12.3.2 数据库连接失败
- 场景:数据库维护期间任务执行失败
- 处理方案 :
- 使用指数退避算法进行重试
- 记录详细的错误日志
- 任务状态自动恢复机制
十三、总结与展望
13.1 设计总结
本文介绍了一个简单易用的定时任务模块设计,主要特点包括:
- 清晰的分层架构:API层、服务层、调度器层职责分明,便于维护和扩展
- 灵活的执行器机制:通过接口实现任务类型的扩展,支持插件化开发
- 完善的任务管理:支持任务的CRUD、状态控制、立即执行等完整生命周期管理
- 可靠的结果处理:异步保存执行结果,支持重试、超时控制和阻塞策略
- 良好的可观测性:完整的日志记录和结果追踪,便于问题排查
- 优秀的前后端集成:提供完整的API接口和前端页面,开箱即用
13.2 技术亮点
- 并发安全设计:使用读写锁保护共享数据,支持高并发场景
- 优雅关闭机制:支持平滑关闭,确保所有任务执行完成
- 配置化驱动:所有任务参数均可配置,无需修改代码
- 插件化架构:支持通过插件扩展执行器,便于功能扩展
- 完善的错误处理:支持重试、超时、错误记录等机制
13.3 未来展望
- 分布式调度:支持多节点部署,实现任务的高可用和负载均衡
- 可视化编排:提供图形化界面,支持复杂任务流程的编排
- 智能调度:基于历史执行数据,智能调整任务执行时间和资源分配
- 监控告警增强:集成更强大的监控告警系统,支持多种通知方式
- 性能优化:支持任务执行结果的压缩存储,减少存储空间占用
13.4 适用场景
本定时任务模块适用于以下场景:
- 企业内部系统的定时任务管理
- 中小型项目的定时任务需求
- 需要灵活扩展执行器类型的场景
- 对任务执行可靠性和可观测性有要求的项目
参考代码
- 调度器接口:
app/global/app/scheduler_interface.go - 任务模型:
app/utils/schedulerhelper/job.go - 调度器实现:
app/utils/schedulerhelper/scheduler.go - 执行器注册:
app/scheduler/register.go - 结果处理:
app/scheduler/result_handler.go - 任务控制器:
app/controllers/sysjobscontroller.go - 任务服务:
app/service/sysjobsservice.go - 参数模型:
app/models/sysjobsparam.go - 路由配置:
app/routes/routes.go
作者注:本文基于实际项目代码整理,详细介绍了如何设计一个简单易用的定时任务模块。该设计已经在生产环境中稳定运行,支撑了多种类型的定时任务。希望本文的设计思路能够对大家有所帮助,如有疑问欢迎交流讨论。