go - cron定时任务

本文重点

  1. go结合cron实现定时任务
  2. gin结合cron实现定时任务
  3. 定时任务cron执行不成功?
  4. 定时任务原理(todo, 有时间再补充)

传送门

cron代码库
cron文档V3
cron文档

go结合cron实现定时任务

简单示例

话不多说, 先上一个最简单, 开箱即用的例子

  1. 相关依赖 go get github.com/robfig/cron/[email protected]

  2. 示例代码

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/robfig/cron/v3"
    )
    
    func main() {
    	c := cron.New()
    	c.AddFunc("@every 3s", func() { fmt.Printf("Every 3 seconds, %s\n", time.Now().Format("15:04:05")) })
    	go c.Start()
    
    	c.AddFunc("@every 2s", func() { fmt.Printf("Every 2 seconds, %s\n", time.Now().Format("15:04:05")) })
    	
    	defer c.Stop()
    	select {}
    }
    	
    
  3. 运行代码
    go run main.go

  4. 运行结果

    Every 2 seconds, 01:17:37
    Every 3 seconds, 01:17:38
    Every 2 seconds, 01:17:39
    Every 2 seconds, 01:17:41
    Every 3 seconds, 01:17:41
    Every 2 seconds, 01:17:43
    

gin结合cron实现定时任务

简单示例

话不多说, 先上一个最简单, 开箱即用的例子

  1. 相关依赖 go get github.com/robfig/cron/[email protected]
  2. 示例代码
    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/robfig/cron/v3"
    )
    
    func main() {
    	c := cron.New()
    	// 每5秒执行一次
    	c.AddFunc("@every 5s", func() { fmt.Printf("Every five seconds, %s\n", time.Now().Format("15:04:05")) })
    	c.Start()
    
    	// 每10秒执行一次
    	c.AddFunc("@every 10s", func() { fmt.Printf("Every ten seconds, %s\n", time.Now().Format("15:04:05")) })
    
    	r := gin.Default()
    	r.Run()
    }
    
    
  3. 运行代码
    go run main.go
  4. 运行结果
    [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
    
    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:   export GIN_MODE=release
     - using code:  gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
    [GIN-debug] Listening and serving HTTP on :8080
    Every five seconds, 01:17:20
    Every five seconds, 01:17:25
    Every ten seconds, 01:17:25
    Every five seconds, 01:17:30
    Every ten seconds, 01:17:35
    Every five seconds, 01:17:35
    Every five seconds, 01:17:40
    
cron 表达式
字段名 是否必填 允许的值 允许的特殊符号
秒 Seconds(v3默认不支持) 0-59 * / , -
分Minutes 0-59 * / , -
时 Hours 0-23 * / , -
日 Day of month 1-31 * / , - ?
月 Month 1-12 or JAN-DEC * / , -
周几 Day of week 0-6 or SUN-SAT * / , - ?
特殊字符说明
  1. asterisk ( * )
    ”, 如:每分钟, 每小时

    The asterisk indicates that the cron expression will match for all values of the field; e.g., using an asterisk in the 5th field (month) would indicate every month.

  2. Slash ( / )
    每隔”, */5可以根据所在位置, 可表示每隔5分钟, 每隔5小时等
    结合"-", 3-59/15(放着分钟位置的时候)表示第3分钟和此后每隔15分钟都执行, 知道59分钟为止

    Slashes are used to describe increments of ranges. For example 3-59/15 in the 1st field (minutes) would indicate the 3rd minute of the hour and every 15 minutes thereafter. The form “*/…” is equivalent to the form “first-last/…”, that is, an increment over the largest possible range of the field. The form “N/…” is accepted as meaning “N-MAX/…”, that is, starting at N, use the increment until the end of that specific range. It does not wrap around.

  3. Comma ( , )
    分隔符, 多个值的时候, 用来当分隔符

    Commas are used to separate items of a list. For example, using “MON,WED,FRI” in the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.

  4. Hyphen ( - )
    范围, 1-17, 可以表示, 凌晨1点到下午5点的每个小时

    Hyphens are used to define ranges. For example, 9-17 would indicate every hour between 9am and 5pm inclusive.

  5. Question mark ( ? )
    *

    Question mark may be used instead of ‘*’ for leaving either day-of-month or day-of-week blank.

预定义的cron时间表
Entry Description Equivalent To
@yearly (or @annually) Run once a year, midnight, Jan. 1st 0 0 0 1 1 *
@monthly Run once a month, midnight, first of month 0 0 0 1 * *
@weekly Run once a week, midnight between Sat/Sun 0 0 0 * * 0
@daily (or @midnight) Run once a day, midnight 0 0 0 * * *
@hourly Run once an hour, beginning of hour 0 0 * * * *

定时任务cron执行不成功?

背景

并非啥特殊需求, 纯粹是写demo的时候, 想写个每秒/每隔几秒执行一次的任务, 能明显看到定时任务是否添加成功和成功执行

问题代码
package main

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/robfig/cron/v3"
)

func main() {
	c := cron.New()
	c.AddFunc("* * * * * *", func() { fmt.Println("Every seconds") })
	c.AddFunc("@every 5s", func() { fmt.Printf("Every five seconds, %s\n", time.Now().Format("15:04:05")) })
	c.Start()

	c.AddFunc("@every 10s", func() { fmt.Printf("Every ten seconds, %s\n", time.Now().Format("15:04:05")) })

	r := gin.Default()
	r.Run()
}

结果:

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
Every five seconds, 01:17:00
Every ten seconds, 01:17:05
Every five seconds, 01:17:05
Every five seconds, 01:17:10
Every five seconds, 01:17:15
Every ten seconds, 01:17:15

问题来了! 我每秒执行一次的定时任务被谁吞了呢?

问题定位

由于我用的是 cron/v3, 去看了看文档, 发现有这么一段说明, 大概意思就是, cron v3默认不支持秒级别的定时任务

Since adding Seconds is the most common modification to the standard cron spec, cron provides a builtin function to do that, which is equivalent to the custom parser you saw earlier, except that its seconds field is REQUIRED:
cron.New(cron.WithSeconds())

解决方案
  1. 使用v3文档推荐的cron.New(cron.WithSeconds())
    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/robfig/cron/v3"
    )
    
    func main() {
    	// c := cron.New()
    	c := cron.New(cron.WithSeconds())
    	c.AddFunc("* * * * * *", func() { fmt.Println("Every seconds") })
    	c.AddFunc("@every 5s", func() { fmt.Printf("Every five seconds, %s\n", time.Now().Format("15:04:05")) })
    	c.Start()
    
    	c.AddFunc("@every 10s", func() { fmt.Printf("Every ten seconds, %s\n", time.Now().Format("15:04:05")) })
    
    	r := gin.Default()
    	r.Run()
    }
    
  2. 改用v1或者v2版本的cron

这里推荐方案1

问题源码分析

看了看, 现在使用的是cron.New()
cron.New()源码如下:

func New(opts ...Option) *Cron {
	c := &Cron{
		entries:   nil,
		chain:     NewChain(),
		add:       make(chan *Entry),
		stop:      make(chan struct{}),
		snapshot:  make(chan chan []Entry),
		remove:    make(chan EntryID),
		running:   false,
		runningMu: sync.Mutex{},
		logger:    DefaultLogger,
		location:  time.Local,
		parser:    standardParser,
	}
	for _, opt := range opts {
		opt(c)
	}
	return c
}

可以看到其中的parser用的是standardParser, standardParser定义如下, 可以看出, 第一个参数不是, 是

var standardParser = NewParser(
	Minute | Hour | Dom | Month | Dow | Descriptor,
)

再看了看, cron.WithSeconds()的源码, 可以明显看出, 是修改了cron的parser

// WithSeconds overrides the parser used for interpreting job schedules to
// include a seconds field as the first one.
func WithSeconds() Option {
	return WithParser(NewParser(
		Second | Minute | Hour | Dom | Month | Dow | Descriptor,
	))
}

// WithParser overrides the parser used for interpreting job schedules.
func WithParser(p Parser) Option {
	return func(c *Cron) {
		c.parser = p
	}
}

你可能感兴趣的:(go,golang,cron,定时任务)