当前位置: 首页 > news >正文

[每周一更]-(第144期):Go 定时任务的使用:从基础到进阶

在这里插入图片描述

文章目录

    • 一、使用 time 包实现定时任务
      • 1.1 使用 `time.Ticker`
      • 1.2 使用 `time.After`
    • 二、使用 cron 表达式调度任务(推荐)
      • 2.1 安装
      • 2.2 基本用法
    • 正确做法:使用 `sync.WaitGroup` 或 `signal.Notify`
      • 方法一:使用 `os.Signal` 优雅监听退出信号
      • 方法二:主线程保活 + 发起 API 请求等逻辑
    • 不推荐仅用 `select {}` 的场景
    • 总结
      • 2.3 Cron 表达式格式
      • 2.4 使用带秒的 Cron 表达式
    • 三、任务控制:停止、重启、带上下文
    • 四、多个任务调度
    • 五、进阶:结合业务任务使用
    • 六、总结
    • 七、建议实践
    • **八、Go 定时任务实战项目(完整版)**,包括:
      • 项目结构
    • 1. 配置文件支持(config/config.yaml)
    • 2. main.go
    • 3. tasks/api_task.go
    • 4. tasks/report_task.go
    • 5. Dockerfile
    • 6. systemd 服务支持(cron.service)
      • 可选扩展点
      • 项目打包
    • 资源

常规使用习惯,大概率都是crontab脚本来分离业务逻辑,但是有时候在具体项目代码中,也会使用到定时任务的操作,以下几个我用过的方式来分享一下:

在日常开发中,我们经常会遇到 定时执行任务 的需求,比如:

  • 每天凌晨备份数据
  • 每隔 10 秒轮询服务状态
  • 每周一清理日志文件

虽然 Go 标准库没有专门的任务调度包,但它提供了丰富的时间处理功能,配合第三方库可实现非常强大的定时任务系统。本文将介绍:

  1. Go 原生定时器的使用
  2. time.Tickertime.After
  3. 基于 cron 表达式的定时任务
  4. 多任务调度与取消
  5. 推荐的第三方库:robfig/cron

一、使用 time 包实现定时任务

Go 标准库中的 time 包提供了简单易用的时间控制函数。

1.1 使用 time.Ticker

每隔固定时间执行一次任务:

package mainimport ("fmt""time"
)func main() {ticker := time.NewTicker(5 * time.Second)defer ticker.Stop()for {select {case t := <-ticker.C:fmt.Println("执行任务时间:", t)}}
}

上面代码会每 5 秒执行一次任务,直到程序退出。


1.2 使用 time.After

只执行一次任务(延迟执行):

func main() {fmt.Println("等待5秒...")time.Sleep(5 * time.Second)fmt.Println("开始执行任务")
}

二、使用 cron 表达式调度任务(推荐)

Go 没有内置 cron 表达式解析器,但 robfig/cron 是最流行的调度库之一,功能强大,语法熟悉。

2.1 安装

go get github.com/robfig/cron/v3

2.2 基本用法

package mainimport ("fmt""github.com/robfig/cron/v3"
)func main() {c := cron.New()// 每分钟执行一次c.AddFunc("* * * * *", func() {fmt.Println("每分钟执行一次任务")})c.Start()// 阻塞主线程(示例中直接 sleep)select {}
}

以上是单独定时任务使用,里边的select{} ,是 Go 中一种阻塞主线程、保持程序运行的常见写法,但它的确会导致 主 goroutine 被永久挂起,从而无法继续执行其他逻辑(比如发起 API 请求、控制台交互等)。

单独使用引起整个项目中其他逻辑挂起,所以一般通过如下替代:

正确做法:使用 sync.WaitGroupsignal.Notify

下面是两种推荐方式,既能阻塞主线程防止退出,又能支持优雅退出、处理请求等行为


方法一:使用 os.Signal 优雅监听退出信号

package mainimport ("fmt""os""os/signal""syscall""github.com/robfig/cron/v3"
)func main() {c := cron.New()c.AddFunc("*/10 * * * * *", func() {fmt.Println("每10秒执行一次任务")})c.Start()// 监听退出信号(支持后续请求或中止)quit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)fmt.Println("定时任务已启动,按 Ctrl+C 退出...")<-quitfmt.Println("收到退出信号,停止定时任务")c.Stop()
}

适合实际应用,能处理任务 + 后续请求 + 优雅退出。


方法二:主线程保活 + 发起 API 请求等逻辑

如果你有主逻辑,比如 Web 服务或某个任务,可以这样写:

func main() {// 启动定时任务startCronTask()// 启动你的主服务,如监听 HTTP 或进行其他处理startHTTPServer()// 保持主进程运行(或处理退出信号)select {} // 或用 signal.Notify 替代
}

不推荐仅用 select {} 的场景

使用 select {} 虽然可以快速阻塞主线程,但无法:

  • 退出程序
  • 接受信号
  • 执行并发任务控制

总结

场景建议写法
快速测试任务可用 select {} 临时阻塞
实际服务、API任务signal.Notify 监听退出
主线程还有其他任务(如 Web)多 goroutine + channel 控制

2.3 Cron 表达式格式

标准 5 字段格式(秒可选):

* * * * *     → 分 时 日 月 星期

示例:

表达式含义
0 0 * * *每天 0 点执行一次
*/10 * * * *每 10 分钟执行一次
30 9 * * 1每周一 9:30 执行

2.4 使用带秒的 Cron 表达式

c := cron.New(cron.WithSeconds())
c.AddFunc("*/5 * * * * *", func() {fmt.Println("每5秒执行一次任务")
})

三、任务控制:停止、重启、带上下文

你可以通过 cron.EntryID 来管理任务,例如取消某个任务:

id, _ := c.AddFunc("*/1 * * * *", func() {fmt.Println("正在执行任务")
})c.Remove(id) // 移除定时任务

也可以使用带 context.Context 的任务,实现超时控制。


四、多个任务调度

你可以添加多个不同任务:

c := cron.New()c.AddFunc("0 * * * *", func() {fmt.Println("每小时整点执行")
})
c.AddFunc("30 9 * * *", func() {fmt.Println("每天早上9:30执行")
})
c.Start()

五、进阶:结合业务任务使用

假设你要实现:每天0点生成报告,可以这样封装:

func generateReport() {// 业务逻辑fmt.Println("生成日报成功")
}func scheduleReportTask() {c := cron.New()c.AddFunc("0 0 * * *", generateReport)c.Start()
}

六、总结

技术方式特点
time.Ticker简单、适合循环任务
time.After单次延迟执行
robfig/cron强大、支持 Cron 表达式、任务管理

七、建议实践

  • 简单轮询:用 time.Ticker
  • 周期任务:用 robfig/cron
  • 支持取消/错误处理:结合 contextlog

八、Go 定时任务实战项目(完整版),包括:

  • API请求 + 日报任务
  • 支持日志输出和错误处理
  • 使用配置文件控制调度逻辑
  • 可部署为服务(带 Dockerfile 和 systemd 示例)
  • 支持日志平台或数据库扩展
  • 使用 context.WithTimeout 控制请求

项目结构

go-cron-service/
├── config/
│   └── config.yaml
├── logs/
│   └── ...
├── tasks/
│   ├── api_task.go
│   └── report_task.go
├── main.go
├── go.mod
├── go.sum
├── Dockerfile
└── cron.service  # systemd服务配置

1. 配置文件支持(config/config.yaml)

api_url: "https://httpbin.org/get"
request_timeout: 5sschedules:api_task: "*/10 * * * * *"report_task: "0 0 0 * * *"

2. main.go

package mainimport ("fmt""os""os/signal""syscall""time""go-cron-service/tasks""gopkg.in/yaml.v3""github.com/robfig/cron/v3""io/ioutil"
)type Config struct {APIURL         string            `yaml:"api_url"`RequestTimeout time.Duration     `yaml:"request_timeout"`Schedules      map[string]string `yaml:"schedules"`
}var AppConfig Configfunc loadConfig() error {data, err := ioutil.ReadFile("config/config.yaml")if err != nil {return err}return yaml.Unmarshal(data, &AppConfig)
}func main() {if err := loadConfig(); err != nil {panic("配置加载失败: " + err.Error())}c := cron.New(cron.WithSeconds())c.AddFunc(AppConfig.Schedules["api_task"], func() {tasks.FetchAPI(AppConfig.APIURL, AppConfig.RequestTimeout)})c.AddFunc(AppConfig.Schedules["report_task"], tasks.GenerateReport)c.Start()fmt.Println("Cron 服务已启动,按 Ctrl+C 退出")quit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<-quitfmt.Println("接收到中断信号,退出...")c.Stop()
}

3. tasks/api_task.go

package tasksimport ("context""fmt""io""net/http""time"
)func FetchAPI(apiURL string, timeout time.Duration) {ctx, cancel := context.WithTimeout(context.Background(), timeout)defer cancel()req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)if err != nil {fmt.Println("构建请求失败:", err)return}resp, err := http.DefaultClient.Do(req)if err != nil {fmt.Println("请求失败:", err)return}defer resp.Body.Close()body, _ := io.ReadAll(resp.Body)fmt.Printf("[%s] 请求成功,长度: %d\n", time.Now().Format("15:04:05"), len(body))
}

4. tasks/report_task.go

package tasksimport ("fmt""os""time"
)func GenerateReport() {now := time.Now().Format("2006-01-02")filePath := fmt.Sprintf("logs/report-%s.txt", now)_ = os.MkdirAll("logs", 0755)file, err := os.Create(filePath)if err != nil {fmt.Println("创建报告失败:", err)return}defer file.Close()content := fmt.Sprintf("日报生成时间:%s\n状态:正常\n", time.Now().Format("2006-01-02 15:04:05"))file.WriteString(content)fmt.Println("报告已生成:", filePath)
}

5. Dockerfile

FROM golang:1.22-alpineWORKDIR /app
COPY . .RUN go build -o cron-service main.goCMD ["./cron-service"]

构建并运行:

docker build -t go-cron-service .
docker run -v $(pwd)/logs:/app/logs go-cron-service

6. systemd 服务支持(cron.service)

[Unit]
Description=Go Cron 定时任务服务
After=network.target[Service]
Type=simple
ExecStart=/usr/local/bin/cron-service
WorkingDirectory=/opt/go-cron-service
Restart=always
StandardOutput=journal
StandardError=journal[Install]
WantedBy=multi-user.target

部署方法:

sudo cp cron.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable cron.service
sudo systemctl start cron.service

可选扩展点

功能说明
日志接入 ELK、Loki使用 logrus/zap 结构化日志
写入数据库可记录任务结果到 MySQL/Postgres
热加载任务使用 fsnotify 监听配置变更
Web 管理界面提供前端管理和动态增删任务(Gin + cron)

项目打包

你可以将此项目上传至 GitHub:

gh repo create go-cron-service --public --source=.
git add .
git commit -m "init: go-cron-service"
git push -u origin main

资源

  • Go time 包官方文档
  • robfig/cron GitHub 项目

相关文章:

  • github-mcp-server v0.5.0 发布详解:远程 GitHub MCP 服务器全新升级与最佳实践
  • Arduino入门教程:4-1、代码基础-进阶
  • PySpark 使用pyarrow指定版本
  • Android-Mod-Menu 使用教程
  • AlibabaCloud+SpringCloud简述
  • 基于Python学习《Head First设计模式》第十三章 现实世界中的模式
  • Python3 学习(菜鸟)-02基本数据类型
  • 基于Python学习《Head First设计模式》第十一章 代理模式
  • 使用Cursor + Devbox + Uniapp 一站式AI编程开发移动端(App、H5、小程序)
  • 分布式定时任务系列12:XXL-job的任务触发为什么是死循环?
  • 动态组件(component)的高级使用
  • 软件测试之简单基础的安全测试方法(另外包含软测面试题库)
  • 【Flutter】程序报错导致的灰屏总结
  • LangChain自动化工作流实战教程:从任务编排到智能决策
  • 计算机网络学习笔记:运输层概述UDP、TCP对比
  • IDEA高效快捷键指南
  • 刚学到一个使用共享软件而禁用弹窗的工具:微软电脑管家
  • 精益数据分析(104/126):免费移动应用的用户活跃率与付费转化优化策略
  • 项目拓展-Jol分析本地对象or缓存的内存占用
  • OCCT基础类库介绍:Modeling Data - 2D Geometry 3D Geometry Topology
  • 陕西高端品牌网站建设/找客源免费用哪个软件好
  • 商河做网站公司/百度的网址是什么
  • 开发微信微网站建设/seo优化工具推荐
  • 服务器网站建设维护/搜狗友链交换
  • 网站会员系统怎么做/百度收录哪些平台比较好
  • 龙泉市住房和城乡建设局网站/网络营销策划方案模板范文