Recently I need to use the timed task function in golang, I use a cron library, the current version is v3, the Internet is quite a lot of v2 tutorials, record the use of the method.

In the old version of the library, the default cron expression is not the standard format, the first bit is the definition of the second level. Now the v3 version can use the standard cron expression directly, mainly see the godoc documentation section

cron Expressions

It is recommended to use online tools to see if the cron you write is right or wrong, simple expressions written directly are generally not a big problem. Here we recommend crontab.guru, which allows you to view your written timing rules in a visual way.

The following is taken from Wikipedia - Cron

1
2
3
4
5
6
7
8
9
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
# │ │ │ │ │                                   7 is also Sunday on some systems)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

Notes. In some systems, Sunday can also be 7 Not very intuitive usage: If both the date and the day of the week are set, the command is executed when one of the conditions is met. Please refer to the following example. The first 5 fields are called the time, day, month and week, and can be easily remembered by individuals. From the sixth field onwards, the command to be executed is specified.

Installation

Nowadays we use Go module for module management, use alt + enter in goland to synchronize the corresponding package github.com/robfig/cron/v3

Use go get to install it as follows

1
go get github.com/robfig/cron/v3

Create Configuration

It is recommended to use the standard cron expressions

1
2
3
4
5
6
// Use the default configuration
c := cron.New()
// It can be configured to skip if the current task is in progress
c := cron.New(cron.WithChain(cron.SkipIfStillRunning(logger)))
// The official definition of seconds for older versions is also available, and this note that the cron expressions you need to pass in are no longer standard cron expressions
c := cron.New(cron.WithSeconds())

In the above code there is a logger, I am using logrus, and in the source code you can see the definition of the logger needed for cron

1
2
3
4
5
6
7
8
// Logger is the interface used in this package for logging, so that any backend
// can be plugged in. It is a subset of the github.com/go-logr/logr interface.
type Logger interface {
	// Info logs routine messages about cron's operation.
	Info(msg string, keysAndValues ...interface{})
	// Error logs an error condition.
	Error(err error, msg string, keysAndValues ...interface{})
}

Then we define a Clog structure and just implement the corresponding interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import (
	"github.com/robfig/cron/v3"
	log "github.com/sirupsen/logrus"
)

type CLog struct {
	clog *log.Logger
}

func (l *CLog) Info(msg string, keysAndValues ...interface{}) {
	l.clog.WithFields(log.Fields{
		"data": keysAndValues,
	}).Info(msg)
}

func (l *CLog) Error(err error, msg string, keysAndValues ...interface{}) {
	l.clog.WithFields(log.Fields{
		"msg":  msg,
		"data": keysAndValues,
	}).Warn(msg)
}

Add task

There are two ways to start a timed task, passing in a function and passing in a task.

Pass-in function

We see the example given in the documentation, you can see that the task is added through c.AddFunc () this function to directly pass a function can see the definition is func (c *Cron) AddFunc (spec string, cmd func ()) (EntryID, error).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Runs at 6am in time.Local
cron.New().AddFunc("0 6 * * ?", ...)

# Runs at 6am in America/New_York
nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))
c.AddFunc("0 6 * * ?", ...)

// AddFunc adds a func to the Cron to be run on the given schedule.
// The spec is parsed using the time zone of this Cron instance as the default.
// An opaque ID is returned that can be used to later remove it.
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
	return c.AddJob(spec, FuncJob(cmd))
}

For example, if you pass in a task that is just a simple function to execute, using AddFunc() will work, and you can also refer to variables outside the function via closures, here is a complete example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

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

func TestCron() {
	c := cron.New()
	i := 1
	c.AddFunc("*/1 * * * *", func() {
		fmt.Println("Execute every minute", i)
		i++
	})
	c.Start()
	time.Sleep(time.Minute * 5)
}
func main() {
	TestCron()
}

/* output

Execute every minute 1
Execute every minute 2
Execute every minute 3
Execute every minute 4
Execute every minute 5
*/

Incoming tasks

But what if we need to keep other information inside the defined task, we can use the AddJob() function and trace the source code definition.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// AddJob adds a Job to the Cron to be run on the given schedule.
// The spec is parsed using the time zone of this Cron instance as the default.
// An opaque ID is returned that can be used to later remove it.
func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) {
	schedule, err := c.parser.Parse(spec)
	if err != nil {
		return 0, err
	}
	return c.Schedule(schedule, cmd), nil
}

// You can see that you need to pass two parameters, `spec` is the cron expression, Job type we do not seem to have seen, click in to see
// Job is an interface for submitted cron jobs.
type Job interface {
	Run()
}

Now we know that our timed task only needs to implement the Run() function, so we can give our own Job definition

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Job struct {
	A    int      `json:"a"`
	B    int      `json:"b"`
	C    string   `json:"c"`
	Shut chan int `json:"shut"`
}

// implement Run() interface to start rsync job
func (this Job) Run() {
	this.A++
	fmt.Printf("A: %d\n", this.A)
	*this.B++
	fmt.Printf("B: %d\n", *this.B)
	*this.C += "str"
	fmt.Printf("C: %s\n", *this.C)
}

Code example

I wrapped a StartJob function to facilitate my own management, but of course multiple tasks can be added at c.AddJob(), all of which will be executed at the request of cron

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package main

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

// Timed task planning
/*
- spec,Pass in the cron time settings
- job,Corresponding tasks
*/
func StartJob(spec string, job Job) {
	logger := &CLog{clog: log.New()}
	logger.clog.SetFormatter(&log.TextFormatter{
		FullTimestamp:   true,
		TimestampFormat: "2006-01-02 15:04:05",
	})
	c := cron.New(cron.WithChain(cron.SkipIfStillRunning(logger)))

	c.AddJob(spec, &job)

	// Initiate execution tasks
	c.Start()
	// Close scheduled tasks on exit
	defer c.Stop()

	// If you use select{} then it will always loop
	select {
	case <-job.Shut:
		return
	}
}

func StopJob(shut chan int) {
	shut <- 0
}

type CLog struct {
	clog *log.Logger
}

func (l *CLog) Info(msg string, keysAndValues ...interface{}) {
	l.clog.WithFields(log.Fields{
		"data": keysAndValues,
	}).Info(msg)
}

func (l *CLog) Error(err error, msg string, keysAndValues ...interface{}) {
	l.clog.WithFields(log.Fields{
		"msg":  msg,
		"data": keysAndValues,
	}).Warn(msg)
}

type Job struct {
	A    int      `json:"a"`
	B    int      `json:"b"`
	C    string   `json:"c"`
	Shut chan int `json:"shut"`
}

// implement Run() interface to start job
func (j *Job) Run() {
	j.A++
	fmt.Printf("A: %d\n", j.A)
	j.B++
	fmt.Printf("B: %d\n", j.B)
	j.C += "str"
	fmt.Printf("C: %s\n", j.C)
}

func main() {
	job1 := Job{
		A:    0,
		B:    1,
		C:    "",
		Shut: make(chan int, 1),
	}
	// Execute every minute
	go StartJob("*/1 * * * *", job1)
	time.Sleep(time.Minute * 3)
}
/*
output

A: 1
B: 2
C: str
A: 2
B: 3
C: strstr
A: 3
B: 4
C: strstrstr
*/

Summary

  • The v3 version of this cron library uses the standard cron expressions directly
  • There are two ways to start a cron task, passing in a function and passing in a job.

References


Reference https://blog.cugxuan.cn/2020/06/04/Go/golang-cron-v3/