golang cron v3 定時任務
最近需要在 golang 中使用定時任務功能,用到了一個 cron[1] 庫,當前是 v3 版本,網上挺多都是 v2 的教程,記錄一下使用方法。
在舊版本的庫中默認的 cron 表達式不是標準格式,第一個位是秒級的定義。現在 v3 版本直接用標準 cron 表示式即可,主要看 godoc 文檔部分 [2]
cron 表示式
推薦使用在線工具來看自己寫的 cron 對不對,簡單的表達式直接寫一般問題不大。這裏推薦 crontab.guru[3],可以通過可視化的方式來查看你編寫的定時規則。
crontab-guru
以下內容摘自維基百科 - Cron[4]
# 文件格式說明
# ┌──分鐘(0 - 59)
# │ ┌──小時(0 - 23)
# │ │ ┌──日(1 - 31)
# │ │ │ ┌─月(1 - 12)
# │ │ │ │ ┌─星期(0 - 6,表示從週日到週六)
# │ │ │ │ │
# * * * * * 被執行的命令
注:在某些系統裏,星期日也可以爲 7
不很直觀的用法:如果日期和星期同時被設定,那麼其中的一個條件被滿足時,指令便會被執行。請參考下例。
前 5 個域稱之分時日月周,可方便個人記憶。
從第六個域起,指明要執行的命令。
安裝
現在都是用的 Go module 進行模塊的管理,直接在 goland 中使用 alt + 回車即可同步對應的包 "github.com/robfig/cron/v3"
使用 go get 安裝方式如下
go get github.com/robfig/cron/v3
創建配置
建議使用標準的 cron 表達式
// 使用默認的配置
c := cron.New()
// 可以配置如果當前任務正在進行,那麼跳過
c := cron.New(cron.WithChain(cron.SkipIfStillRunning(logger)))
// 官方也提供了舊版本的秒級的定義,這個注意你需要傳入的 cron 表達式不再是標準 cron 表達式
c := cron.New(cron.WithSeconds())
在上面的代碼中出現了一個 logger,我使用的是 logrus,在源碼中可以看到 cron 需要的 logger 的定義
// Logger is the interface used in this package for logging, so that any backend
// can be plugged in. It is a subset of the github.com/go-logr/logr interface.
type Logger interface {
// Info logs routine messages about cron's operation.
Info(msg string, keysAndValues ...interface{})
// Error logs an error condition.
Error(err error, msg string, keysAndValues ...interface{})
}
那麼我們定義了一個 Clog 結構體,實現對應的接口就行了
import (
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
)
type CLog struct {
clog *log.Logger
}
func (l *CLog) Info(msg string, keysAndValues ...interface{}) {
l.clog.WithFields(log.Fields{
"data": keysAndValues,
}).Info(msg)
}
func (l *CLog) Error(err error, msg string, keysAndValues ...interface{}) {
l.clog.WithFields(log.Fields{
"msg": msg,
"data": keysAndValues,
}).Warn(msg)
}
添加任務
啓動定時任務有兩種方法,分別是傳入函數和傳入任務。
傳入函數
我們看到文檔中給出的範例,可以看到任務的添加是通過 c.AddFunc()
這個函數來進行的,直接傳入一個函數即可,可以看到定義是 func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)
。
# Runs at 6am in time.Local
cron.New().AddFunc("0 6 * * ?", ...)
# Runs at 6am in America/New_York
nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))
c.AddFunc("0 6 * * ?", ...)
// AddFunc adds a func to the Cron to be run on the given schedule.
// The spec is parsed using the time zone of this Cron instance as the default.
// An opaque ID is returned that can be used to later remove it.
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
return c.AddJob(spec, FuncJob(cmd))
}
舉個例子,如果你傳入的任務僅僅就是一個簡單函數進行執行,使用 AddFunc()
就行了,同時也可以通過閉包來引用函數外面的變量,下面是一個完整的例子。
package main
import (
"fmt"
"github.com/robfig/cron/v3"
"time"
)
func TestCron() {
c := cron.New()
i := 1
c.AddFunc("*/1 * * * *", func() {
fmt.Println("每分鐘執行一次", i)
i++
})
c.Start()
time.Sleep(time.Minute * 5)
}
func main() {
TestCron()
}
/* output
每分鐘執行一次 1
每分鐘執行一次 2
每分鐘執行一次 3
每分鐘執行一次 4
每分鐘執行一次 5
*/
傳入任務
但是如果我們定義的任務裏面還需要留存其他信息呢,可以使用 AddJob()
這個函數,追溯一下源碼定義。
// AddJob adds a Job to the Cron to be run on the given schedule.
// The spec is parsed using the time zone of this Cron instance as the default.
// An opaque ID is returned that can be used to later remove it.
func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) {
schedule, err := c.parser.Parse(spec)
if err != nil {
return 0, err
}
return c.Schedule(schedule, cmd), nil
}
// 可以看到需要傳入兩個參數,`spec` 就是 cron 表達式,Job 類型我們好像還沒見過,點進去看
// Job is an interface for submitted cron jobs.
type Job interface {
Run()
}
現在知道我們的定時任務只需要實現 Run()
這個函數就行了,所以我們可以給出自己的 Job 定義
type Job struct {
A int `json:"a"`
B int `json:"b"`
C string `json:"c"`
Shut chan int `json:"shut"`
}
// implement Run() interface to start rsync job
func (this Job) Run() {
this.A++
fmt.Printf("A: %d\n", this.A)
*this.B++
fmt.Printf("B: %d\n", *this.B)
*this.C += "str"
fmt.Printf("C: %s\n", *this.C)
}
代碼例子
給出一個完整代碼的示例,我封裝了一個 StartJob 函數,方便自己的管理,當然在 c.AddJob()
處可添加多個任務,都會 cron 的要求執行
package main
import (
"fmt"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"time"
)
// 定時任務計劃
/*
- spec,傳入 cron 時間設置
- job,對應執行的任務
*/
func StartJob(spec string, job Job) {
logger := &CLog{clog: log.New()}
logger.clog.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
TimestampFormat: "2006-01-02 15:04:05",
})
c := cron.New(cron.WithChain(cron.SkipIfStillRunning(logger)))
c.AddJob(spec, &job)
// 啓動執行任務
c.Start()
// 退出時關閉計劃任務
defer c.Stop()
// 如果使用 select{} 那麼就一直會循環
select {
case <-job.Shut:
return
}
}
func StopJob(shut chan int) {
shut <- 0
}
type CLog struct {
clog *log.Logger
}
func (l *CLog) Info(msg string, keysAndValues ...interface{}) {
l.clog.WithFields(log.Fields{
"data": keysAndValues,
}).Info(msg)
}
func (l *CLog) Error(err error, msg string, keysAndValues ...interface{}) {
l.clog.WithFields(log.Fields{
"msg": msg,
"data": keysAndValues,
}).Warn(msg)
}
type Job struct {
A int `json:"a"`
B int `json:"b"`
C string `json:"c"`
Shut chan int `json:"shut"`
}
// implement Run() interface to start job
func (j *Job) Run() {
j.A++
fmt.Printf("A: %d\n", j.A)
j.B++
fmt.Printf("B: %d\n", j.B)
j.C += "str"
fmt.Printf("C: %s\n", j.C)
}
func main() {
job1 := Job{
A: 0,
B: 1,
C: "",
Shut: make(chan int, 1),
}
// 每分鐘執行一次
go StartJob("*/1 * * * *", job1)
time.Sleep(time.Minute * 3)
}
/*
output
A: 1
B: 2
C: str
A: 2
B: 3
C: strstr
A: 3
B: 4
C: strstrstr
*/
總結
-
這個 cron 庫的 v3 版本直接使用標準 cron 表達式即可
-
啓動 cron 任務有傳入函數和傳入任務兩種方法,如果需要管理建議實現自己的 Job 類
參考資料
-
robfig/cron[5]
-
godoc-cron[6]
-
crontab.guru[7]
[1] cron: https://github.com/robfig/cron
[2] godoc 文檔部分: https://godoc.org/github.com/robfig/cron
[3] crontab.guru: https://crontab.guru/
[4] 維基百科 - Cron: https://zh.wikipedia.org/wiki/Cron
[5] robfig/cron: https://github.com/robfig/cron
[6] godoc-cron: http://godoc.org/github.com/robfig/cron
[7] crontab.guru: https://crontab.guru/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ag9FIrSWoh49BG5awy642w