github.com/robfig/cron/v3 是一个功能强大且易于使用的定时任务管理库。本文进一步介绍robfig/cron在定时任务一些主要功能、如何使用它以及一些实际应用场景的例子。主要包括

  1. 添加任务方法AddJob
  2. 指定执行时间
  3. 动态添加和删除任务
  4. Option选项
  5. JobWrapper与DefaultWrapper

AddJob添加任务

Cron实例可以通过调用AddJob() 方法用于添加一个实现了 Job 接口的对象作为任务,Example:

package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"time"
)

type GreetingJob struct {
	Name string
}

func (g GreetingJob) Run() {
	fmt.Println("Hi: ", g.Name, "now:", time.Now().String())
}

func main() {
	c := cron.New()
	entityID, err := c.AddJob("@every 2s", GreetingJob{"Greeter"})
	if err != nil {
		fmt.Errorf("error : %v", err)
		return
	}
	fmt.Println("entityID:", entityID)
	c.Start()
	defer c.Stop()
	select {}
}

//output:
//entityID: 1
//Hi:  Greeter now: 2023-03-04 17:50:07.0169185 +0800 CST m=+1.716253301
//Hi:  Greeter now: 2023-03-04 17:50:09.0068064 +0800 CST m=+3.706141201

此例中,GreetingJob实现了cron.Job 接口,cron实例调用AddJob,加入一个每2秒执行的任务,务并传递参数。

指定执行时间

可以通过修改 cron 表达式来指定任务的执行时间。Example:

c := cron.New(cron.WithSeconds()) 
entityID, err := c.AddFunc("0 05 18 * * *", func() {
	fmt.Println("hi now:", time.Now().String())
})

此例中,将在每天的傍晚18点5分执行指定的函数。

动态添加和删除任务

cron/v3 允许开发人员在运行时动态添加和删除任务。Example:

package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"time"
)

func main() {
	c := cron.New(cron.WithSeconds())
	c.Start()
	defer c.Stop()
	count := 0
     // 添加第一个任务
	entityID1, err := c.AddFunc("*/2 * * * * *", func() {
		count++
		fmt.Println("Job1: ", count, "now:", time.Now().String())
	})
	must(err)
	fmt.Println("entityID1:", entityID1)
     // 等待 6 秒钟,让第一个任务执行3次
	time.Sleep(time.Second * 6)
    // 添加第二个任务
	entityID2, err := c.AddFunc("*/2 * * * * *", func() {
		count++
		fmt.Println("Job2: ", count, "now:", time.Now().String())
	})
	must(err)
     // 等待 10秒钟,让两个任务交替执行几次
	time.Sleep(time.Second * 10)
     // 删除第2个任务
	c.Remove(entityID2)
     // 等待 10秒钟,发现只有任务1在执行了
	time.Sleep(time.Second * 10)
     // 删除第2个任务
	c.Remove(cron.EntryID(1))
	fmt.Println("entityID2", entityID2)
    //发现只有没有任务在执行了 
	select {}
}

func must(err error) {
	if err != nil {
		panic(any(err))
	}
}

Option选项

Option 是一种用于配置 Cron 实例的结构体类型。Option 类型有多个可选字段,可用于配置定时任务的行为,上例中有使用到的WithSeconds。

以下是 Option 类型的一些字段及其说明:

  • WithSeconds():在 cron 表达式中包含秒(0-59),默认为不包含秒。
  • WithLocation():设置时区,可以使用标准时区名称或时区偏移量。
  • WithChain():将多个函数连接成单个函数。
  • WithParser():指定 cron 表达式的解析器,默认为 StandardParser。
  • WithLogger():指定日志记录器,默认为 DefaultLogger。

下面 WithLogger Option Cron Example:

package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"log"
	"os"
	"time"
)

func main() {
	c := cron.New(
		cron.WithLogger(
			cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))))
	c.AddFunc("@every 1s", func() {
		fmt.Println("hello world")
	})
	c.Start()
	defer c.Stop()

	time.Sleep(3 * time.Second)
}
//output:
//cron: 2023/03/04 21:31:17 start
//cron: 2023/03/04 21:31:17 schedule, now=2023-03-04T21:31:17+08:00, entry=1, next=2023-03-04T21:31:18+08:00
//cron: 2023/03/04 21:31:18 wake, now=2023-03-04T21:31:18+08:00
//cron: 2023/03/04 21:31:18 run, now=2023-03-04T21:31:18+08:00, entry=1, next=2023-03-04T21:31:19+08:00
//hello world

上例中,调用cron.VerbosPrintfLogger()包装log.Logger,这个logger会详细记录cron内部的调度过程

JobWrapper与DefaultWrapper

//NewChain返回一个由给定JobWrappers组成的Chain,类似中间件。

type JobWrapper func(Job) Job

type Chain struct {
	wrappers []JobWrapper
}

func NewChain(c ...JobWrapper) Chain {
	return Chain{c}
}

cron内置了 3 个用得比较多的JobWrapper

  • Recover 捕获内部Job产生的 panic
  • DelayIfStillRunning 序列化作业,延迟后续的运行直到前一个是完整的。
  • SkipIfStillRunning 跳过Job的调用,如果之前的调用是仍在运行。 以Reover为例:
package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
)

func main() {
	c := cron.New(
		cron.WithSeconds(),
		cron.WithChain(
			cron.Recover(cron.DefaultLogger),
		),
	)

	c.AddFunc("@every 2s", func() {
		fmt.Println("Start...")
		panic(any("ohno...."))
		fmt.Println("End...")
	})

	c.Start()
	defer c.Stop()

	select {}
}

output:

Start...
cron: 2023/03/04 21:59:32 panic, error=ohno...., stack=...
goroutine 7 [running]:
github.com/robfig/cron/v3.Recover.func1.1.1()
	E:/gopath/pkg/mod/github.com/robfig/cron/v3@v3.0.1/chain.go:45 +0xa5
panic({0x7370c0, 0x75b930})
	...

在上例中,使用 cron.Recover() 方法来捕获任务执行过程中的 panic,并记录日志。如果不进行 panic 捕获的话,程序将会因为 panic 而退出。需要注意的是,当使用 Recover() 方法时,不应该让任务函数返回一个 error,否则它将不会被正确地捕获。如果任务函数可能会返回 error,建议使用 Try() 方法进行捕获。

以上。

参考

Go Corntab

本文简要介绍golang 使用crontab实现定时任务。

linux crontab

Linux crontab 是用来定期执行程序的命令。当安装完成操作系统之后,默认便会启动此任务调度命令

命令语法:

crontab [ -u user ] file

crontab [ -u user ] { -l | -r | -e }

*    *    *    *    *
-    -    -    -    -
|    |    |    |    |
|    |    |    |    +----- 星期中星期几 (0 - 6) (星期天 为0)
|    |    |    +---------- 月份 (1 - 12) 
|    |    +--------------- 一个月中的第几天 (1 - 31)
|    +-------------------- 小时 (0 - 23)
+------------------------- 分钟 (0 - 59)

go cron

第三方包“github.com/robfig/cron”来创建 crontab,以实现定时任务

package main

import (
	"fmt"
	"github.com/robfig/cron"
)

func main() {
	var (
		cronS = cron.New()
		spec  = "*/2 * * * * "
		count = 0
	)

	entityID, err := cronS.AddFunc(spec, func() {
		count++
		fmt.Println("count: ", count,"now:", time.Now().Unix())
	})
	if err != nil {
		fmt.Errorf("error : %v", err)
		return
	}
	cronS.Start()

	fmt.Println(entityID)
	defer cronS.Stop()
	select {}
}

go run main.go

输出:

count:  1 now: 1658053860
count:  2 now: 1658053920
count:  3 now: 1658053980
count:  4 now: 1658054040
count:  5 now: 1658054100

可以看到每隔1分钟,执行一次Func,count++

默认情况下标准 cron 规范解析(第一个字段是“分钟”) 可以轻松选择进入秒字段。

cronS = cron.New(cron.WithSeconds())
//注意这里多了一个参数
spec  = "*/2 * * * * * "

执行输出

count:  1 now: 1658053640
count:  2 now: 1658053642
count:  3 now: 1658053644
count:  4 now: 1658053646
count:  5 now: 1658053648 

可以看到每隔两秒执行一次

jefffff

Stay hungry. Stay Foolish COOL

Go backend developer

China Amoy