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 的实现原理和更加复杂的用法

posted @ 2019-11-22 12:27  jssyjam  阅读(25272)  评论(0编辑  收藏  举报