golang高效Cron表达式解析与任务调度插件gronx的使用

Golang高效Cron表达式解析与任务调度插件gronx的使用

gronx 是一个Golang的cron表达式解析器,具有任务运行器和守护进程功能,支持类似crontab的任务列表文件。它可以从任何时间点找到表达式下一次(NextTick())或上一次(PrevTick())的运行时间。

主要特性

  • 零依赖
  • 非常快速,因为它在段不匹配时会提前退出
  • 内置类似crontab的守护进程
  • 支持秒级时间粒度

安装

go get -u github.com/adhocore/gronx

基本用法

import (
	"time"

	"github.com/adhocore/gronx"
)

gron := gronx.New()
expr := "* * * * *"

// 检查表达式是否有效,返回bool
gron.IsValid(expr) // true

// 检查表达式是否在当前时间到期,返回bool和error
gron.IsDue(expr) // true|false, nil

// 检查表达式在给定时间是否到期
gron.IsDue(expr, time.Date(2021, time.April, 1, 1, 1, 0, 0, time.UTC)) // true|false, nil

也可以不实例化直接检查有效性:

import "github.com/adhocore/gronx"

gronx.IsValid("* * * * *") // true

批量到期检查

如果有多个cron表达式需要在同一参考时间检查是否到期,可以使用BatchDue()

gron := gronx.New()
exprs := []string{"* * * * *", "0 */5 * * * *"}

// 返回[]gronx.Expr{}数组,每个项目都有Due标志和遇到的Err
dues := gron.BatchDue(exprs)

for _, expr := range dues {
    if expr.Err != nil {
        // 处理错误
    } else if expr.Due {
        // 处理到期任务
    }
}

// 或者使用给定时间
ref := time.Now()
gron.BatchDue(exprs, ref)

下一次运行时间

找出cron表达式下一次运行时间(在不久的将来):

allowCurrent = true // 包括当前时间
nextTime, err := gronx.NextTick(expr, allowCurrent) // 返回time.Time, error

// 或者在某个参考时间后的下一次运行
refTime = time.Date(2022, time.November, 1, 1, 1, 0, 0, time.UTC)
allowCurrent = false // 排除参考时间
nextTime, err := gronx.NextTickAfter(expr, refTime, allowCurrent) // 返回time.Time, error

上一次运行时间

找出cron表达式上一次运行时间(在不久的过去):

allowCurrent = true // 包括当前时间
prevTime, err := gronx.PrevTick(expr, allowCurrent) // 返回time.Time, error

// 或者在某个参考时间前的上一次运行
refTime = time.Date(2022, time.November, 1, 1, 1, 0, 0, time.UTC)
allowCurrent = false // 排除参考时间
nextTime, err := gronx.PrevTickBefore(expr, refTime, allowCurrent) // 返回time.Time, error

Go Tasker

Tasker是一个可以在Golang应用程序中以编程方式使用的任务管理器。它作为守护进程运行,并调用使用cron表达式调度的任务:

package main

import (
	"context"
	"time"

	"github.com/adhocore/gronx/pkg/tasker"
)

func main() {
	taskr := tasker.New(tasker.Option{
		Verbose: true,
		// 可选:默认为本地时区
		Tz:      "Asia/Bangkok",
		// 可选:默认为stderr日志流
		Out:     "/full/path/to/output-file",
	})

	// 添加每分钟运行的任务
	taskr.Task("* * * * *", func(ctx context.Context) (int, error) {
		// 做一些事情...

		// 然后返回退出码和错误,例如:如果一切正常
		return 0, nil
	}).Task("*/5 * * * *", func(ctx context.Context) (int, error) { // 每5分钟
		// 你也可以将输出记录到上面Option中配置的Out文件:
		taskr.Log.Printf("done something in %d s", 2)

		return 0, nil
	})

	// 运行任务不重叠,将concurrent标志设置为false:
	concurrent := false
	taskr.Task("* * * * * *", tasker.Taskify("sleep 2", tasker.Option{}), concurrent)

	// 每10分钟执行任意命令
	taskr.Task("@10minutes", taskr.Taskify("command --option val -- args", tasker.Option{Shell: "/bin/sh -c"}))

	// ... 添加更多任务

	// 可选地,如果你想让tasker在2小时后停止,使用Until()传递持续时间:
	taskr.Until(2 * time.Hour)

	// 最后运行tasker,它每分钟精确地触发并运行该时间到期的所有任务!
	// 当收到ctrl+c时,它会优雅地退出,确保待处理的任务完成。
	taskr.Run()
}

并发性

默认情况下,任务可以并发运行,即如果上一次运行尚未完成但现在又到期了,它将再次运行。如果你想一次只运行一个任务实例,将concurrent标志设置为false:

taskr := tasker.New(tasker.Option{})

concurrent := false
expr, task := "* * * * * *", tasker.Taskify("php -r 'sleep(2);'")
taskr.Task(expr, task, concurrent)

任务守护进程

它也可以作为独立的任务守护进程使用,而不是在Golang应用程序中以编程方式使用。

首先,安装tasker命令:

go install github.com/adhocore/gronx/cmd/tasker@latest

然后准备一个crontab格式的taskfile,最后像这样运行任务守护进程:

tasker -file path/to/taskfile

Tasker命令选项:

-file string <required>
    The task file in crontab format
-out string
    The fullpath to file where output from tasks are sent to
-shell string
    The shell to use for running tasks (default "/usr/bin/bash")
-tz string
    The timezone to use for tasks (default "Local")
-until int
    The timeout for task daemon in minutes
-verbose
    The verbose mode outputs as much as possible

示例:

tasker -verbose -file path/to/taskfile -until 120 # 运行直到120分钟后(即2小时),所有反馈回显
tasker -verbose -file path/to/taskfile -out path/to/output # 所有反馈回显到输出文件
tasker -tz America/New_York -file path/to/taskfile -shell zsh # 使用zsh shell基于纽约时区运行所有任务

Cron表达式

完整的cron表达式由7个部分组成:

<second> <minute> <hour> <day> <month> <weekday> <year>

然而,通常5部分就足够了,5部分表达式解释为:

<minute> <hour> <day> <month> <weekday>

在这种情况下,会在<second>位置前置一个默认值0。

在6部分表达式中,如果第6部分匹配<year>(即至少4位数字),它将被解释为:

<minute> <hour> <day> <month> <weekday> <year>

并且会在<second>位置前置一个默认值0。

对于每个部分,你可以有多个选择,用逗号分隔:

例如:0 0,30 * * * *表示第0或第30分钟

要指定值范围,可以使用破折号:

例如:0 10-15 * * * *表示第10、11、12、13、14和15分钟

要指定步长范围,可以结合破折号和斜杠:

例如:0 10-15/2 * * * *表示10到15分钟之间每2分钟一次,即第10、12和14分钟

对于<day><weekday>部分,还有额外的修饰符(可选)。

如果你想,你可以在一个表达式中混合多个选择、范围和步长:

0 5,12-20/4,55 * * * *匹配分钟为5或12-20/4或55中的任意一个

真实缩写

你可以使用真实的缩写(3个字符)表示月份和星期几。例如:JANdecfriSUN

标签

以下标签可用,它们在解析前会转换为真实的cron表达式:

  • @yearly@annually - 每年
  • @monthly - 每月
  • @daily - 每天
  • @weekly - 每周
  • @hourly - 每小时
  • @5minutes - 每5分钟
  • @10minutes - 每10分钟
  • @15minutes - 每15分钟
  • @30minutes - 每30分钟
  • @always - 每分钟
  • @everysecond - 每秒

为了向后兼容,@always目前仍表示每分钟,在未来的版本中它可能表示每秒

// 像这样使用标签:
gron.IsDue("@hourly")
gron.IsDue("@5minutes")

修饰符

支持以下修饰符

  • 月份中的某天 / 5部分中的第3部分 / 6+部分中的第4部分:

    • L 表示月份的最后一天(例如:L在闰年二月可能表示29日)
    • W 表示最近的工作日(例如:10W是最接近10日的工作日(MON-FRI))
  • 星期几 / 5部分中的第5部分 / 6+部分中的第6部分:

    • L 表示月份的最后一周几(例如:2L是最后一个星期二)
    • # 表示月份中的第n个星期几(例如:1#2是第二个星期一)

许可证

© MIT | 2021-2099, Jitendra Adhikari


更多关于golang高效Cron表达式解析与任务调度插件gronx的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高效Cron表达式解析与任务调度插件gronx的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang高效Cron表达式解析与任务调度插件gronx使用指南

1. 什么是gronx

gronx是一个轻量级、高效的Golang库,用于解析Cron表达式并执行定时任务。相比标准库的cron包,gronx具有以下优势:

  • 更快的解析速度
  • 更低的内存占用
  • 支持秒级精度
  • 更灵活的调度选项

2. 安装gronx

go get github.com/adhocore/gronx

3. 基本使用方法

3.1 简单任务调度

package main

import (
	"fmt"
	"time"
	
	"github.com/adhocore/gronx"
)

func main() {
	gron := gronx.New()

	// 定义任务
	task := func() {
		fmt.Println("任务执行于:", time.Now().Format("2006-01-02 15:04:05"))
	}

	// 每5秒执行一次
	expr := "*/5 * * * * *"

	// 启动调度器
	gron.AddFunc(expr, task)
	gron.Start()

	// 运行10分钟后停止
	time.Sleep(10 * time.Minute)
	gron.Stop()
}

3.2 检查Cron表达式是否有效

package main

import (
	"fmt"
	
	"github.com/adhocore/gronx"
)

func main() {
	gron := gronx.New()
	
	expressions := []string{
		"*/5 * * * * *",  // 有效
		"* * * * *",      // 有效(默认分钟级)
		"60 * * * * *",   // 无效(秒数超出范围)
	}
	
	for _, expr := range expressions {
		valid := gron.IsValid(expr)
		fmt.Printf("表达式 '%s' 是否有效: %t\n", expr, valid)
	}
}

4. 高级功能

4.1 带参数的任务

package main

import (
	"fmt"
	"time"
	
	"github.com/adhocore/gronx"
)

func main() {
	gron := gronx.New()

	// 带参数的任务函数
	greet := func(name string) {
		fmt.Printf("Hello, %s! 现在时间是: %s\n", 
			name, time.Now().Format("15:04:05"))
	}

	// 每10秒执行一次
	gron.AddFunc("*/10 * * * * *", func() {
		greet("Gopher")
	})

	gron.Start()
	defer gron.Stop()

	// 运行5分钟
	time.Sleep(5 * time.Minute)
}

4.2 一次性任务

package main

import (
	"fmt"
	"time"
	
	"github.com/adhocore/gronx"
)

func main() {
	gron := gronx.New()

	// 在下一分钟的0秒执行
	nextMinute := time.Now().Truncate(time.Minute).Add(time.Minute)
	expr := fmt.Sprintf("%d * * * * *", nextMinute.Second())

	gron.AddFunc(expr, func() {
		fmt.Println("一次性任务执行于:", time.Now().Format("2006-01-02 15:04:05"))
	})

	gron.Start()
	defer gron.Stop()

	// 等待足够时间让任务执行
	time.Sleep(2 * time.Minute)
}

4.3 任务标签管理

package main

import (
	"fmt"
	"time"
	
	"github.com/adhocore/gronx"
)

func main() {
	gron := gronx.New()

	// 添加带标签的任务
	gron.AddTaggedFunc("job1", "*/15 * * * * *", func() {
		fmt.Println("Job1执行:", time.Now().Format("15:04:05"))
	})

	gron.AddTaggedFunc("job2", "*/20 * * * * *", func() {
		fmt.Println("Job2执行:", time.Now().Format("15:04:05"))
	})

	gron.Start()

	// 5秒后移除job1
	time.Sleep(5 * time.Second)
	gron.Remove("job1")

	// 运行2分钟后停止
	time.Sleep(2 * time.Minute)
	gron.Stop()
}

5. 性能优化技巧

  1. 避免在任务函数中执行耗时操作:长时间运行的任务会阻塞后续任务的执行

  2. 使用Worker Pool:对于可能耗时的任务,可以使用worker pool来并发执行

package main

import (
	"fmt"
	"sync"
	"time"
	
	"github.com/adhocore/gronx"
)

func main() {
	gron := gronx.New()
	
	// 创建worker pool
	workers := 5
	jobs := make(chan func(), 100)
	
	var wg sync.WaitGroup
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for job := range jobs {
				job()
			}
		}()
	}
	
	// 添加任务到worker pool
	gron.AddFunc("*/10 * * * * *", func() {
		jobs <- func() {
			// 模拟耗时任务
			time.Sleep(3 * time.Second)
			fmt.Println("Worker处理任务:", time.Now().Format("15:04:05"))
		}
	})
	
	gron.Start()
	
	// 运行10分钟后停止
	time.Sleep(10 * time.Minute)
	close(jobs)
	wg.Wait()
	gron.Stop()
}

6. 与标准库cron的对比

特性 gronx 标准库cron
解析速度 更快 较慢
内存占用 更低 较高
秒级精度 支持 不支持
语法检查
任务标签管理 支持 不支持

7. 总结

gronx是一个高效、灵活的Golang定时任务调度库,特别适合需要高性能和精细控制的场景。通过本文的介绍,您应该已经掌握了gronx的基本用法和高级特性,可以在实际项目中根据需求选择合适的调度方案。

对于大多数应用场景,gronx都能提供出色的性能表现,特别是当您需要秒级精度或大量定时任务时,gronx的优势会更加明显。

回到顶部