Go语言定时任务cron的设计与使用

 更新时间:2023年11月06日 10:33:14   作者:小范真是一把好手  
这篇文章主要为大家详细介绍了Go语言中定时任务cron的设计与使用,文中的示例代码讲解详细,对我们深入掌握Go语言有一定的帮助,需要的可以参考下

一、Cron表达式

Field name   | Mandatory? | Allowed values  | Allowed special characters
----------   | ---------- | --------------  | --------------------------
Seconds      | Yes        | 0-59            | * / , -
Minutes      | Yes        | 0-59            | * / , -
Hours        | Yes        | 0-23            | * / , -
Day of month | Yes        | 1-31            | * / , - ?
Month        | Yes        | 1-12 or JAN-DEC | * / , -
Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?

Cron表达式的格式是通过六个字符表示:"1 * * * * *"。这六位数分别表示秒,分,小时,每月第几天,月,每个星期第几天;

在这里重点解释一下特殊字符:

  • *:代表任意值;*在分钟字段,表示每分钟;
  • /:用来指定时间间隔,*/15在分钟字段,表示每隔15分钟;
  • ,:列出多个离散值,1,15在天字段,表示每月1号和15号;
  • -:定义某个范围,9-17在小时字段,表示上午9点到下午5点,两边都是闭区间;
  • ?:表示无特定值。在Cron中,如果天数与星期的指定会互斥。看下面两个例子:

0 0 12 ? * WED - 表示每周三中午12点。关心星期,忽略天数;

0 0 12 15 * ? - 表示每个月的第15天中午12点。关心天数,忽略星期;

同时在"github.com/robfig/cron/v3"包中预定义的Schedule,如下所示:

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包

func TestCron(t *testing.T) {
	c := cron.New(cron.WithSeconds())

	// 每分钟第一秒执行该任务
	c.AddFunc("1 * * * * *", func() {
		fmt.Println("Hello world!")
	})

    // 每10s执行一次任务
	sh := cron.Every(10 * time.Second)
	c.Schedule(sh, cron.FuncJob(func() {
		fmt.Println("you are ok")
	}))

	go func() {
		ticker := time.NewTicker(time.Second * 4)
		for {
			select {
			case <-ticker.C:
				fmt.Println("length: ", len(c.Entries()))
			}
		}
	}()

	// c.Start()
	c.Start()

	// Wait for the Cron job to run
	time.Sleep(5 * time.Minute)

	// Stop the Cron job scheduler
	c.Stop()
}

上述示例代码中,使用两种创建定时任务的方式,分别是:

  • c.AddFunc()
  • c.Schedule()

cron包的使用非常简单,你只需要提供Job以及其执行的规则即可。

三、如何设计一个Cron

关于Cron,调用者所有的操作与系统执行对应的任务之间是异步的。因此,对于调用者来说,系统用例如下:

更进一步,可以查看下Cron提供的API:

type Cron struct {
	// Has unexported fields.
}
    Cron keeps track of any number of entries, invoking the associated func as
    specified by the schedule. It may be started, stopped, and the entries may
    be inspected while running.

func New(opts ...Option) *Cron
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error)
func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error)
func (c *Cron) Entries() []Entry
func (c *Cron) Entry(id EntryID) Entry
func (c *Cron) Location() *time.Location
func (c *Cron) Remove(id EntryID)
func (c *Cron) Run()
func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID
func (c *Cron) Start()
func (c *Cron) Stop() context.Context

调用者添加完所有任务之后,系统的处理流程如下(从后台任务的角度看):

上述就是后台任务的流程,简化后的代码如下:

func (c *Cron) run() {
	// Figure out the next activation times for each entry.
	now := c.now()

	for {
		// Determine the next entry to run.
		// 将所有任务,按照下一次运行时间排序
    sort.Sort(byTime(c.entries))
    
		for {
			select {
			case now = <-timer.C:
				now = now.In(c.location)
				c.logger.Info("wake", "now", now)

				// Run every entry whose next time was less than now
				for _, e := range c.entries {
					if e.Next.After(now) || e.Next.IsZero() {
						break
					}
					c.startJob(e.WrappedJob)
					e.Prev = e.Next
					e.Next = e.Schedule.Next(now)
					c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next)
				}

    	// 新增一个任务
			case newEntry := <-c.add:
      	....
        // 添加任务到数组容器
        
    	 // 获取当前时刻,Cron里面所有的定时任务
			case replyChan := <-c.snapshot:
				replyChan <- c.entrySnapshot()
				continue

    	// 停止Cron
			case <-c.stop:
      	...
				return

    	// 移除某个定时任务
			case id := <-c.remove:
        ....
				c.removeEntry(id)

			}

			break
		}
	}
}

四、学习点

1. 通过channel传输快照

func (c *Cron) Entries() []Entry {
    c.runningMu.Lock()
    defer c.runningMu.Unlock()

    // 如果Cron,正在运行,那么返回一个通道
    if c.running {
        replyChan := make(chan []Entry, 1)
        c.snapshot <- replyChan
        return <-replyChan
    }

    // 如果Cron,已经结束了,直接返回所有Entry
    return c.entrySnapshot()
}

这种写法特别有意思。当调用者想查看当前系统所有的任务时,系统返回的是一个通道,接着在通道中返回所有的数据。具体时序图如下所示:

下面这个架构图画的不是很好,画都画了就放这吧。

2. 匹配规则

读到cron这个项目,你是否有这样的疑问?cron后台任务根据调用给定的规则,如何执行任务的呢?比如"* * * * 1 *",系统是如何知道每年的第一个月执行相应的任务呢?下面代码,以月份为例。

程序的大致流程:

  • 将月份规则转化为二进制数值;
  • 通过当前时间不断+1,直到匹配规则月份;

这里主要借助下面这个函数:

func getBits(min, max, step uint) uint64 {
    var bits uint64

    // If step is 1, use shifts.
    if step == 1 {
        return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
    }

    // Else, use a simple loop.
    for i := min; i <= max; i += step {
        bits |= 1 << i
    }
    return bits
}

func TestGetBits(t *testing.T) {
    res := getBits(1, 3, 1)

    fmt.Printf("%d 的二进制表示是 %b\n", res, res)
}

3. 实现接口的函数

// Job is an interface for submitted cron jobs.
type Job interface {
    Run()
}

type FuncJob func()

func (f FuncJob) Run() { f() }

上述代码定义Job接口、FuncJob类型,并且函数类型实现了Job接口。这种写法很常见,比如http.HandleFunc。这样写的好处,能够将一个函数强转之后直接丢到接口参数中,具体转化流程如下:

func() 类型函数 -- 强转:FuncJob(func()) -- FuncJob -- 可以丢进Job接口中;

到此这篇关于Go语言定时任务cron的设计与使用的文章就介绍到这了,更多相关Go定时任务cron内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang error使用场景介绍

    Golang error使用场景介绍

    我们在使用Golang时,不可避免会遇到异常情况的处理,与Java、Python等语言不同的是,Go中并没有try...catch...这样的语句块,这个时候我们如何才能更好的处理异常呢?本文来教你正确方法
    2023-03-03
  • 详解Go中gin框架如何实现带颜色日志

    详解Go中gin框架如何实现带颜色日志

    当我们在终端上(比如Goland)运行gin框架搭建的服务时,会发现输出的日志是可以带颜色的,那这是如何实现的呢?本文就来和大家简单讲讲
    2023-04-04
  • Go语言实现配置热加载的方法分享

    Go语言实现配置热加载的方法分享

    web项目,经常需要热启动各种各样的配置信息,一旦这些服务发生变更,我们需要重新启动web server,以使配置生效,实现配置热加载,本文为大家整理了几个方法实现这个需求,需要的可以参考下
    2023-05-05
  • Golang使用原生http实现中间件的代码详解

    Golang使用原生http实现中间件的代码详解

    中间件(middleware):常被用来做认证校验、审计等,家常用的Iris、Gin等web框架,都包含了中间件逻辑,但有时我们引入该框架显得较为繁重,本文将介绍通过golang原生http来实现中间件操作,需要的朋友可以参考下
    2024-05-05
  • Go语言中的UTF-8实现

    Go语言中的UTF-8实现

    这篇文章主要介绍了Go语言中的UTF-8实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Go语言利用heap实现优先级队列

    Go语言利用heap实现优先级队列

    这篇文章主要为大家详细介绍了Go语言中heap的使用以及如何利用heap实现优先级队列的相关资料,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-05-05
  • 三种Golang数组拷贝的实现方式与性能分析

    三种Golang数组拷贝的实现方式与性能分析

    在 Golang 中,有多种方式可以进行数组的拷贝,本文将对其中的三种方式进行性能分析,并比较它们的优缺点,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-06-06
  • Go语言的GOPATH与工作目录详解

    Go语言的GOPATH与工作目录详解

    这篇文章主要介绍了Go语言的GOPATH与工作目录详解,本文详细讲解了GOPATH设置、应用目录结构、编译应用等内容,需要的朋友可以参考下
    2014-10-10
  • Go语言利用aicli实现轻松调用DeepSeek和ChatGPT

    Go语言利用aicli实现轻松调用DeepSeek和ChatGPT

    这篇文章主要为大家介绍了一款用Go语言编写的AI助手客户端库——aicli,该库不仅支持ChatGPT,还集成了DeepSeek,感兴趣的小伙伴可以了解一下
    2025-03-03
  • Golang之模糊测试工具的使用

    Golang之模糊测试工具的使用

    本文主要介绍了Golang之模糊测试工具的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03

最新评论