簡介
在Linux中,Cron是計划任務管理系統,通過crontab命令使任務在約定的時間執行已經計划好的工作,例如定時備份系統數據、周期性清理緩存、定時重啟服務等。
本文介紹的cron庫是一個用於管理定時任務的庫,就是用Go實現Linux中crontab命令的相似效果。
快速使用
文本代碼使用 Go Modules。
創建目錄並初始化:
$ mkdir cron && cd cron
$ go mod init cron
安裝cron,目前最新穩定版本為 v3:
$ go get -u github.com/robfig/cron/v3
在項目中導入包使用:
package main
import (
"fmt"
"time"
"github.com/robfig/cron/v3"
)
func main() {
c := cron.New()
c.AddFunc("@every 1s", func() {
fmt.Println("tick every 1 second")
})
c.Start()
// 阻塞,或者使用其他延遲時間函數、
select{}
}
使用非常簡單,創建cron對象,這個對象用於管理定時任務。
調用cron對象的AddFunc()方法向管理器中添加定時任務。AddFunc()接受兩個參數,參數 1 以字符串形式指定觸發時間規則,參數 2 是一個無參的函數,每次觸發時調用。@every 1s表示每秒觸發一次,@every后加一個時間間隔,表示每隔多長時間觸發一次。例如@every 1h表示每小時觸發一次,@every 1m2s表示每隔 1 分 2 秒觸發一次。time.ParseDuration()支持的格式都可以用在這里。
調用c.Start()啟動定時循環。
注意一點,因為c.Start()啟動一個新的 goroutine 做循環檢測,我們在代碼最后加了一行time.Sleep(time.Second * 5)防止主 goroutine 退出。
運行效果,每隔 1s 輸出一行字符串:
$ go run main.go
tick every 1 second
tick every 1 second
tick every 1 second
tick every 1 second
tick every 1 second
時間格式
與Linux 中crontab命令相似,cron庫支持用 5 個空格分隔的域來表示時間。
| 字段名 | 是否必須 | 允許的值 | 允許的特定字符 |
|---|---|---|---|
| 秒(Seconds) | 是 | 0-59 | * / , - |
| 分(Minute) | 是 | 0-59 | * / , - |
| 時(Hours) | 是 | 0-23 | * / , - |
| 日(Day of month) | 是 | 1-31 | * / , - ? |
| 月(Month) | 是 | 1-12 或 JAN-DEC | * / , - |
| 星期(Day of week) | 否 | 0-6 或 SUM-SAT | * / , - ? |
注意,月份和周歷名稱都是不區分大小寫的,也就是說SUN/Sun/sun表示同樣的含義(都是周日)。
特殊字符含義如下:
- 星號():使用的域可以匹配任何值,例如將月份域(第 5 個)設置為*,表示每個月;
- 斜線(/):用來指定范圍的步長,例如如第1個字段(minutes) 值是 3-59/15,表示每小時的第3分鍾開始執行一次,之后每隔 15 分鍾執行一次(即 3、18、33、48 這些時間點執行),這里也可以表示為:3/15;
- 逗號(,):用來列舉一些離散的值和多個范圍,例如將周歷的域(第 6 個)設置為MON,WED,FRI表示周一、三和五;
- 連字號(-):用來表示范圍,例如將小時的域(第 3 個)設置為9-17表示上午 9 點到下午 17 點(包括 9 和 17);
- 問號(?):只能用在月歷和周歷的域中,用來代替*,表示每月/周的任意一天。
- (L,W,#): Go中沒有L,W,#的用法
了解規則之后,我們可以定義任意時間:
每隔5秒執行一次:*/5 * * * * ?
每隔1分鍾執行一次:0 */1 * * * ?
每天23點執行一次:0 0 23 * * ?
每天凌晨1點執行一次:0 0 1 * * ?
每月1號凌晨1點執行一次:0 0 1 1 * ?
在26分、29分、33分執行一次:0 26,29,33 * * * ?
每天的0點、13點、18點、21點都執行一次:0 0 0,13,18,21 * * ?
記熟了這幾個域的順序,再多練習幾次很容易就能掌握格式。熟悉規則了之后,就能熟練使用crontab命令了。
func main() {
c := cron.New()
c.AddFunc("30 * * * *", func() {
fmt.Println("Every hour on the half hour")
})
c.AddFunc("30 3-6,20-23 * * *", func() {
fmt.Println("On the half hour of 3-6am, 8-11pm")
})
c.AddFunc("0 0 1 1 *", func() {
fmt.Println("Jun 1 every year")
})
c.Start()
for {
time.Sleep(time.Second)
}
}
預定義時間規則
為了方便使用,cron預定義了一些時間規則:
- @yearly:也可以寫作@annually,表示每年第一天的 0 點。等價於0 0 1 1 *;
- @monthly:表示每月第一天的 0 點。等價於0 0 1 * *;
- @weekly:表示每周第一天的 0 點,注意第一天為周日,即周六結束,周日開始的那個 0 點。等價於0 0 * * 0;
- @daily:也可以寫作@midnight,表示每天 0 點。等價於0 0 * * *;
- @hourly:表示每小時的開始。等價於0 * * * *。
例如:
func main() {
c := cron.New()
c.AddFunc("@hourly", func() {
fmt.Println("Every hour")
})
c.AddFunc("@daily", func() {
fmt.Println("Every day on midnight")
})
c.AddFunc("@weekly", func() {
fmt.Println("Every week")
})
c.Start()
for {
time.Sleep(time.Second)
}
}
上面代碼只是演示用法,實際運行可能要等待非常長的時間才能有輸出。
注意:這樣使用一個
cron.New()的定時任務,執行的每個方法是順序執行的,也就是說並不是同一時刻開始執行
固定時間間隔
cron支持固定時間間隔,格式為:
@every <duration>
含義為每隔duration觸發一次。<duration>會調用time.ParseDuration()函數解析,所以ParseDuration支持的格式都可以。例如1h30m10s。
時區
默認情況下,所有時間都是基於當前時區的。當然我們也可以指定時區,有 2 兩種方式:
- 在時間字符串前面添加一個
CRON_TZ=+ 具體時區,具體時區的格式在之前carbon的文章中有詳細介紹。東京時區為Asia/Tokyo,紐約時區為America/New_York; - 創建
cron對象時增加一個時區選項cron.WithLocation(location),location為time.LoadLocation(zone)加載的時區對象,zone為具體的時區格式。或者調用已創建好的cron對象的SetLocation()方法設置時區。
示例:
func main() {
nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))
c.AddFunc("0 6 * * ?", func() {
fmt.Println("Every 6 o'clock at New York")
})
c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", func() {
fmt.Println("Every 6 o'clock at Tokyo")
})
c.Start()
for {
time.Sleep(time.Second)
}
}
