golang 实现定时任务

Posted jssyjam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 的实现原理和更加复杂的用法

以上是关于golang 实现定时任务的主要内容,如果未能解决你的问题,请参考以下文章

golang cron定时任务简单实现

Golang定时任务简单实现

Golang 定时任务管理

基于gocron实现的定时任务服务降级方案

Golang 定时器

代码片段 - Golang 实现简单的 Web 服务器