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 HTML 模板使用指南示例详解

    Golang HTML 模板使用指南示例详解

    本文详细介绍了Golang HTML模板的使用方法,包括基础模板、高级模板、完整应用示例、CSS样式、JavaScript交互等,文章强调了模板组织、代码复用、语义化HTML、响应式设计、性能优化等最佳实践,感兴趣的朋友跟随小编一起看看吧
    2025-01-01
  • Golang流程控制语句的具体使用

    Golang流程控制语句的具体使用

    在编写程序时,流程控制是必不可少的一部分,本文主要介绍了Golang流程控制语句的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • golang利用pprof与go-torch如何做性能分析

    golang利用pprof与go-torch如何做性能分析

    这篇文章主要给大家介绍了关于golang利用pprof与go-torch如何做性能分析的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • 源码剖析Golang如何fork一个进程

    源码剖析Golang如何fork一个进程

    创建一个新进程分为两个步骤,一个是fork系统调用,一个是execve 系统调用,本文将从源码的角度带大家剖析一下Golang是如何fork一个进程的
    2023-06-06
  • Golang网络模型netpoll源码解析(具体流程)

    Golang网络模型netpoll源码解析(具体流程)

    本文介绍了Golang的网络模型netpoll的实现原理,本文将从为什么需要使用netpoll模型,以及netpoll的具体流程实现两个主要角度来展开学习,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • Golang中切片长度和容量的区别示例详解

    Golang中切片长度和容量的区别示例详解

    切片长度与容量在Go中很常见,切片长度是切片中可用元素的数量,而切片容量是从切片中第一个元素开始计算的底层数组中的元素数量,这篇文章主要给大家介绍了关于Golang中切片长度和容量区别的相关资料,需要的朋友可以参考下
    2024-01-01
  • Go外部依赖包从vendor,$GOPATH和$GOPATH/pkg/mod查找顺序

    Go外部依赖包从vendor,$GOPATH和$GOPATH/pkg/mod查找顺序

    这篇文章主要介绍了Go外部依赖包vendor,$GOPATH和$GOPATH/pkg/mod下查找顺序,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang TCP粘包拆包问题的解决方法

    Golang TCP粘包拆包问题的解决方法

    这篇文章主要给大家介绍了Golang TCP粘包拆包问题的解决方法,文中通过示例代码介绍的非常详细,对大家学习或者使用Golang具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • Golang中切片的用法与本质详解

    Golang中切片的用法与本质详解

    Go的切片类型为处理同类型数据序列提供一个方便而高效的方式,下面这篇文章就来给大家介绍了关于Golang中切片的用法与本质的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-07-07
  • golang框架中跨服务的最佳通信协议和工具

    golang框架中跨服务的最佳通信协议和工具

    在 go 框架中实现跨服务通信的最佳实践包括使用 grpc(适用于低延迟高吞吐量)、http 客户端(适用于 restful api)和消息队列(适用于异步解耦通信),在选择通信方式时,应考虑服务交互模式、性能要求和部署环境等因素
    2024-06-06

最新评论