golang 實現定時任務


在實際開發過程中,我們有時候需要編寫一些定時任務。當然我們可以使用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 恢復操作可以參考 withChaincron.Recover

后記

下一篇詳細解析 cron 的實現原理和更加復雜的用法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM