在實際開發過程中,我們有時候需要編寫一些定時任務。當然我們可以使用crontab
命令實現我們的需求。但是這種方法不滿足一些定制化場景,同時會依賴具體的操作系統環境。
定時任務
在golang
中我們可以使用cron來實現我們定時任務的需求。他的使用方式非常簡單,具體代碼如下:
package main
import(
"fmt"
cron "github.com/robfig/cron/v3"
)
func main() {
crontab := cron.New()
task := func() {
fmt.Println("hello world")
}
// 添加定時任務, * * * * * 是 crontab,表示每分鍾執行一次
crontab.AddFunc("* * * * *", task)
// 啟動定時器
crontab.Start()
// 定時任務是另起協程執行的,這里使用 select 簡答阻塞.實際開發中需要
// 根據實際情況進行控制
select {}
}
注:
- New()函數支持多種初始化選項,比如
cron.WithPanicLogger
- 定時任務是另起協程執行的
上面就是 cron 的最簡單使用示例,如果需要了解更加詳細的用法,可以參考官方文檔和示例。
自定義封裝
在上述的使用方法基礎上,基於我的實際需求,我對cron
庫進行了簡單封裝,主要為實現下面幾個需求:
- 管理所有的定時任務,需要記錄定時任務的編號和相關信息
- 停止一個定時任務
- 支持添加函數類型和接口類型任務
話不多說,直接貼代碼:
package main
import (
"fmt"
"sync"
"github.com/pkg/errors"
cron "github.com/robfig/cron/v3"
)
// Crontab crontab manager
type Crontab struct {
inner *cron.Cron
ids map[string]cron.EntryID
mutex sync.Mutex
}
// NewCrontab new crontab
func NewCrontab() *Crontab {
return &Crontab{
inner: cron.New(),
ids: make(map[string]cron.EntryID),
}
}
// IDs ...
func (c *Crontab) IDs() []string {
c.mutex.Lock()
defer c.mutex.Unlock()
validIDs := make([]string, 0, len(c.ids))
invalidIDs := make([]string, 0)
for sid, eid := range c.ids {
if e := c.inner.Entry(eid); e.ID != eid {
invalidIDs = append(invalidIDs, sid)
continue
}
validIDs = append(validIDs, sid)
}
for _, id := range invalidIDs {
delete(c.ids, id)
}
return validIDs
}
// Start start the crontab engine
func (c *Crontab) Start() {
c.inner.Start()
}
// Stop stop the crontab engine
func (c *Crontab) Stop() {
c.inner.Stop()
}
// DelByID remove one crontab task
func (c *Crontab) DelByID(id string) {
c.mutex.Lock()
defer c.mutex.Unlock()
eid, ok := c.ids[id]
if !ok {
return
}
c.inner.Remove(eid)
delete(c.ids, id)
}
// AddByID add one crontab task
// id is unique
// spec is the crontab expression
func (c *Crontab) AddByID(id string, spec string, cmd cron.Job) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.ids[id]; ok {
return errors.Errorf("crontab id exists")
}
eid, err := c.inner.AddJob(spec, cmd)
if err != nil {
return err
}
c.ids[id] = eid
return nil
}
// AddByFunc add function as crontab task
func (c *Crontab) AddByFunc(id string, spec string, f func()) error {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, ok := c.ids[id]; ok {
return errors.Errorf("crontab id exists")
}
eid, err := c.inner.AddFunc(spec, f)
if err != nil {
return err
}
c.ids[id] = eid
return nil
}
// IsExists check the crontab task whether existed with job id
func (c *Crontab) IsExists(jid string) bool {
_, exist := c.ids[jid]
return exist
}
代碼實現很簡單,每個函數的作用都可以參考注釋.下面簡單實用一下上面的封裝:
type testTask struct {
}
func (t *testTask) Run() {
fmt.Println("hello world")
}
func main() {
crontab := NewCrontab()
// 實現接口的方式添加定時任務
task := &testTask{}
if err := crontab.AddByID("1", "* * * * *", task); err != nil {
fmt.Printf("error to add crontab task:%s", err)
os.Exit(-1)
}
// 添加函數作為定時任務
taskFunc := func() {
fmt.Println("hello world")
}
if err := crontab.AddByFunc("2", "* * * * *", taskFunc); err != nil {
fmt.Printf("error to add crontab task:%s", err)
os.Exit(-1)
}
crontab.Start()
select {}
}
注:
- task id 是唯一的,實際開發可以使用 uuid
- 這個封裝是並發安全的
不足:
- 未支持初始化參數
- 定時任務的錯誤采集,如果某個定時任務出錯,應該能夠獲取到錯誤信息(這里指的是錯誤不是 panic)
- panic 恢復操作可以參考
withChain
和cron.Recover
后記
下一篇詳細解析 cron 的實現原理和更加復雜的用法