Go定时任务库robfig Cron一些主要功能、实际应用场景
github.com/robfig/cron/v3 是一个功能强大且易于使用的定时任务管理库。本文进一步介绍robfig/cron在定时任务一些主要功能、如何使用它以及一些实际应用场景的例子。主要包括
- 添加任务方法AddJob
- 指定执行时间
- 动态添加和删除任务
- Option选项
- 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() 方法进行捕获。
以上。