三十的博客

Go 语言定时任务实现方式详解

本文内容基于 AI 生成结果整理,可能包含不准确信息,仅供参考使用。
发布时间
最后更新
阅读量 加载中...

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

1. time.Ticker(周期性定时器)

time.Ticker 适合需要周期性执行的任务。

go
package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop() // 确保资源释放

	for {
		select {
		case <-ticker.C:
			fmt.Println("定时任务执行:", time.Now().Format("2006-01-02 15:04:05"))
		}
	}
}

特点 ​:

2. time.Timer(单次定时器)

time.Timer 适合只需要执行一次的任务。

go
package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(2 * time.Second)

	<-timer.C
	fmt.Println("定时任务执行:", time.Now().Format("2006-01-02 15:04:05"))

	// 可重置实现循环
	timer.Reset(2 * time.Second)
	<-timer.C
	fmt.Println("第二次执行:", time.Now().Format("2006-01-02 15:04:05"))
}

特点 ​:

3. time.AfterFunc(异步定时任务)

go
package main

import (
	"fmt"
	"time"
)

func task() {
	fmt.Println("定时任务执行:", time.Now().Format("2006-01-02 15:04:05"))
}

func main() {
	timer := time.AfterFunc(3*time.Second, task)

	time.Sleep(4 * time.Second)
	// timer.Stop() // 可取消未执行的任务
}

特点 ​:

二、使用第三方 cron 库(推荐)

对于复杂的定时任务需求,推荐使用 github.com/robfig/cron/v3 库。

基本用法

go
package main

import (
	"fmt"
	"time"

	"github.com/robfig/cron/v3"
)

func main() {
	c := cron.New()

	// 每秒执行
	_, _ = c.AddFunc("@every 1s", func() {
		fmt.Println("每秒执行:", time.Now().Format("2006-01-02 15:04:05"))
	})

	// 每分钟执行
	_, _ = c.AddFunc("* * * * *", func() {
		fmt.Println("每分钟执行:", time.Now().Format("2006-01-02 15:04:05"))
	})

	c.Start()
	select {} // 阻止程序退出
}

生产环境推荐用法

go
package main

import (
	"log"
	"time"

	"github.com/robfig/cron/v3"
)

func main() {
	// 带日志记录的cron实例
	c := cron.New(cron.WithLogger(
		cron.VerbosePrintfLogger(log.New(log.Writer(), "cron: ", log.LstdFlags))))

	_, err := c.AddFunc("@every 1s", func() {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("任务执行出错: %v", r)
			}
		}()

		log.Println("任务开始执行")
		time.Sleep(500 * time.Millisecond)
		log.Println("任务执行完成")
	})

	if err != nil {
		log.Fatal("添加任务失败:", err)
	}

	c.Start()
	defer c.Stop()

	time.Sleep(5 * time.Second)
	log.Println("程序退出")
}

优势 ​:

优雅关闭写法

go
package cron

import (
	"strings"
	"time"

	wechat "gitee.com/iswleii/wechat-weather/internal/wechat/service/types"

	"gitee.com/iswleii/wechat-weather/internal/user"
	"gitee.com/iswleii/wechat-weather/internal/weather"
	"github.com/robfig/cron/v3"
	"go.uber.org/zap"
)

// WeatherCron 天气定时任务结构体
type WeatherCron struct {
	weatherService weather.Service
	userService    user.Service
	cron           *cron.Cron
	logger         *zap.Logger
	wechatService  wechat.Service
}

// NewWeatherCron 创建新的天气定时任务实例
func NewWeatherCron(
	ws weather.Service, 
	us user.Service, 
	logger *zap.Logger, 
	wechatService wechat.Service,
	) *WeatherCron {
	return &WeatherCron{
		weatherService: ws,
		userService:    us,
		cron:           cron.New(cron.WithSeconds()), // 使用秒级精度
		logger:         logger,
		wechatService:  wechatService,
	}
}

// Start 启动定时任务
func (wc *WeatherCron) Start() {
	// 每天早上7点发送天气提醒
	_, err := wc.cron.AddFunc("0 0 7 * * *", func() {
		wc.sendWeatherNotifications("早安!今日天气提醒")
	})
	if err != nil {
		wc.logger.Error("添加早晨天气定时任务失败", zap.Error(err))
	}

	// 每天晚上20点发送天气提醒
	_, err = wc.cron.AddFunc("0 0 20 * * *", func() {
		wc.sendWeatherNotifications("晚安!明日天气预告")
	})
	if err != nil {
		wc.logger.Error("添加晚间天气定时任务失败", zap.Error(err))
	}

	// 启动定时任务
	wc.cron.Start()
	wc.logger.Info("天气定时任务已启动")
}

// Stop 停止定时任务
func (wc *WeatherCron) Stop() {
	if wc.cron != nil {
		ctx := wc.cron.Stop()
		// 等待所有正在执行的任务完成
		select {
		case <-ctx.Done():
			wc.logger.Info("天气定时任务已停止")
		case <-time.After(5 * time.Second):
			wc.logger.Warn("天气定时任务停止超时")
		}
	}
}

// sendWeatherNotifications 发送天气通知给所有订阅用户
func (wc *WeatherCron) sendWeatherNotifications(prefix string) {}

三、定时任务最佳实践

错误处理 ​:任务函数内部应该捕获 panic
日志记录 ​:记录任务开始、结束和错误信息
资源释放 ​:使用 defer 确保定时器/调度器正确关闭
并发控制 ​:长时间任务考虑使用 goroutine 和 sync.WaitGroup
配置化 ​:将定时配置提取到配置文件中

四、方案选型建议

方案 适用场景 优点 缺点
time.Ticker 简单周期性任务 标准库,无需依赖 功能简单
time.Timer 单次或可重置任务 灵活可控 需要手动管理
time.AfterFunc 异步单次任务 使用简单 功能有限
cron 库 复杂定时需求 功能强大 需要引入第三方库

对于大多数生产环境,推荐使用 cron 库,它提供了更丰富的功能和更好的可维护性。

五、常见问题解答

Q:定时任务执行时间过长会影响下次执行吗?​​

A:对于 time.Ticker 和 cron 库,默认会等待当前任务完成再开始下次计时。如果需要并发执行,可以在任务内启动 goroutine。

​Q:如何实现分布式环境下的定时任务?​​

A:标准方案需要借助分布式锁或使用专门的分布式任务调度系统。单机方案无法直接扩展到分布式环境。

​Q:定时任务执行失败后如何重试?​​

A:可以在任务函数内部实现重试逻辑,或者使用支持重试的任务队列系统。

#Golang