Golang如何对cron进行二次封装实现指定时间执行定时任务

本文主要是介绍Golang如何对cron进行二次封装实现指定时间执行定时任务,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误...

背景

go中常用的定时任务库:https://github.com/robfig/cron,不支持指定某个时间开始执行一次任务,然后再以一定的时间间隔开始执行任务。

在真实的业务场景中可能会有这样的需求:进行某个操作时执行一次任务,然后根据这个操作时间每隔一段时间执行一次任务;再次操作时,立即执行一次任务,然后根据此次操作的时间点每隔一段时间执行一次任务,这时就需要停止上一次操作产生的定时间隔执行任务。

下面就根据go中的cron库进行封装来满足上述场景的功能。

cron库下载

go get -u github.com/robfig/cron/v3

代码示例

【1】结构体定义

import (
	"fmt"
	"sync"
	"time"

	"GoTest/comm/logger"

	"github.com/robfig/cron/v3"
	"go.uber.org/zap"
)

const (
	HOUR   = 1
	MINUTE = 2
	SECOND = 3
)

const defaultIntervalTime = 8

type VarCron struct {
	c            *cron.Cron
	jobId        cron.EntryID //执行的任务id
	jobMutex     sync.Mutex   //防止并发问题
	startTime    time.Time    //定时任务从此时间开始执行
	intervalTime uint32       //任务执行间隔时间,不设置默认8小时
	intervalType uint8        //任务执行间隔类型,1-时,2.分,3-秒,不设置默认单位为时
	f            func() error //要执行的任务
}

type OpOption func(*VarCron)

func NewVarCron(f func() error, opts ...OpOption) *VarCron {
	vc := &VarCron{
		c: cron.New(cron.WithSeconds()), //支持秒级调度
		f: f,
	}

	for _, opt := range opts {
		opt(vc)
	}

	return vc
}

func WithIntervalTime(intervalTime uint32) OpOption {
	return func(vc *VarCron) {
		vc.intervalTime = intervalTime
	}
}

func WithIntervalType(intervalType uint8) OpOption {
	return func(vc *VarCron) {
		vc.intervalType = intervalType
	}
}

上面定义一个新的结构体VarCron就是给原生的cron对象增加一些新的属性字段。

字段含义
jobId定时执行任务的id,只支持一个任务,目的是为了方便管理,如果想支持多个,可以多使用NewVarCron初始化多个对象分别执行各自的定时任务
jobMutex保证并发安全
startTime首次执行任务的时间
intervalTime定时任务执行间隔
intervalType定时任务执行类型
f定时任务中执行的函数

【2】定时任务开启

func (v *VarCron) Start(startTime time.Time) {
	v.jobMutex.Lock() //保证同一时刻只有一个在执行
	defer v.jobMutex.Unlock()

	logger.Info("start var cron", zap.Time("last_start_time", v.startTime), zap.Time("this_start_time", startTime))

	//更新开始时间
	v.startTime = startTime

	//停止上一次的定时任务
	v.http://www.chinasem.cnstop()

	now := time.Now()
	if now.After(v.startTime) || now.Equal(v.startTime) { //定时任务指定的执行时间在此刻之前就立即执行
		logger.Info("now time after or equal start time", zap.Time("now_time", now), zap.Time("start_time", v.startTime))

		//立即执行一次
		v.execJob()

		//间隔执行定时任务
		jobId, err := v.c.AddFunc(v.getInterval(), func() {
			v.execJob()
		})
		if err != nil {
			logger.Error("add func error", zap.Error(err))
			return
		}

		v.jobId = jobId

		//开始定时任务
		v.c.Start()
	} else {
		logger.Info("now time before start time", zap.Time("now_time", now), zap.Time("start_time", v.startTime))

		//到指定时间时间之后再开始执行定时任务,精确到某月某天某时某分某秒就行
		jobId, err :=编程 v.c.AddFunc(fmt.Sprintf("%d %d %d %d %d ?", v.startTime.Second(), v.startTime.Minute(), v.startTime.Hour(), v.startTime.Day(), v.startTime.Month()), func() {
			//停止上一次的定时任务
			v.stop()

			//这次是到指定时间之后的执行
			v.execJob()

			//根据间隔开启定时任务
			newJobId, err := v.c.AddFunc(v.pythongetInterval(), func() {
				v.execJob()
			})
			if err != nil {
				logger.Error("add func error", zap.Error(err))
				return
			}

			v.jobId = newJobId
			v.c.Start()
		})
		if err != nil {
			logger.Error("add func error", zap.Error(err))
			return
		}

		v.jobId = jobId
		v.c.Start()
	}
}

func (v *VarCron) stop() {
	if v.c != nil {
		v.c.Remove(v.jobId) //移除任务
		v.c.Stop()          //关闭定时任务
	}
}

func (v *VarCron) execJob() {
	if err := v.f();NRrBAQm err != nil {
		logger.Error("exec job error", zap.Error(err))
	}
}

func (v *VarCron) getInterval() string {
	if v.intervalTime == 0 {
		v.intervalTime = defaultIntervalTime
	}

	switch v.intervalType {
	case HOUR:
		return fmt.Sprintf("@every %dh", v.intervalTime)
	case MINUTE:
		return fmt.Sprintf("@every %dm", v.intervalTime)
	case SECOND:
		return fmt.Sprintf("@every %ds", v.intervalTime)
	}

	return fmt.Sprintf("@every %dh", v.intervalTime)
}

调用Start(startTime time.Time)函数会有两种场景:

  • 场景1:startTime在当前时间之前,这个时候会立即执行一次任务,然后按照间隔定时执行任务。
  • 场景2:startTime在当前时间之后,这个时候会等待时间到达startTime执行一次,然后按照间隔定时执行任务。

上面代码中的stop函数目的就是为了停止上一次的任务,保证不会有冲突的定时任务执行。

【3】使用示例

f := func() error {
		logger.Info("exec task")

		return nil
	}

	//每30秒打印一次时间
	varCron := self_cron.NewVarCron(f, self_cron.WithIntervalType(self_cron.SECOND), self_cron.WithIntervalTime(30))

	fmt.Println("===== 指定开始时间在当前时间之前,会立即执行一次,然后定时执行 ======")
	varCron.Start(time.Now().AddDate(0, 0, -1)) //AddDate(0, 0, -1)的作用是将日期向前推一天

	time.Sleep(3 * time.Minute) //第一种场景定时执行任务跑三分钟

	fmt.Println("===== 指定开始时间在当前时间之后,到指定时间执行一次,然后再开始定时执行 ======")
	varCron.Start(time.Now().Add(time.Minute)) //一分钟后立即执行一次,然后开始定时执行

	time.Sleep(3 * time.Minute) //第二种场景定时执行任务跑三分钟就退出

上面是一个简单使用的例子:注册一个每隔30秒打印字符串exec task的函数;第1次指定开始时间为昨天的这个时刻,这时会立即打印exec task一次,然后每隔30s打印一次exec task;3分钟后第2次指定开始时间为当前时间的1分钟之后,这时上一次的任务已经被取消,1分钟内不会再打印exec task,1分钟后会打印exec task,然后每隔30s打印一次exec task

【4】控制台输出

$ go run ./cron_demo/main.go
===== 指定开始时间在当前时间之前,会立即执行一次,然后定时执行 ======
[2024-10-08 17:23:38.135] | INFphpO | Goroutine:1  | [self_cron/start_cron.go:63] | start var cron | {"last_start_time": "[0001-01-01 00:00:00.000]", "this_start_time": "[2024-10-07 17:23:38.096]"}
[2024-10-08 17:23:38.136] | INFO | Goroutine:1  | [self_cron/start_cron.go:73] | now time after or equal start time | {"now_time": "[2024-10-08 17:23:38.136]", "start_time": "[2024-10-07 17:23:38.096]"}
[2024-10-08 17:23:38.136] | INFO | Goroutine:1  | [cron_demo/main.go:159]      | exec task //指定的执行时间在当前时间之前,立即执行一次,之后每隔30s执行
[2024-10-08 17:24:08.013] | INFO | Goroutine:34 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:24:38.008] | INFO | Goroutine:35 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:25:08.010] | INFO | Goroutine:36 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:25:38.008] | INFO | Goroutine:37 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:26:08.015] | INFO | Goroutine:38 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:26:38.005] | INFO | Goroutine:39 | [cron_demo/main.go:159]      | exec task
===== 指定开始时间在当前时间之后,到指定时间执行一次,然后再开始定时执行 ======
[2024-10-08 17:26:38.144] | INFO | Goroutine:1  | [self_cron/start_cron.go:63] | start var cron | {"last_start_time": "[2024-10-07 17:23:38.096]", "this_start_time": "[2024-10-08 17:27:38.144]"} 
[2024-10-08 17:26:38.145] | INFO | Goroutine:1  | [self_cron/start_cron.go:92] | now time before start time | {"now_time": "[2024-10-08 17:26:38.145]", "start_time": "[2024-10-08 17:27:38.144]"}
[2024-10-08 17:27:38.003] | INFO | Goroutine:5  | [cron_demo/main.go:159]      | exec task //指定时间到了之后才执行,之后每隔30s执行
[2024-10-08 17:28:08.007] | INFO | Goroutine:42 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:28:38.008] | INFO | Goroutine:43 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:29:08.013] | INFO | Goroutine:44 | [cron_demo/main.go:159]      | exec task
[2024-10-08 17:29:38.013] | INFO | Goroutine:45 | [cron_demo/main.go:159]      | exec task

总结

上面给出一个指定一个时间开始执行任务,然后再以一定的时间间隔定时执行任务的功能,可以根据业务场景的需求去修改上述功能,新增一些属性,比如除了动态修改定时任务时间,还可以动态修改时间间隔或执行函数,或者新增一个tag字段打印在日志中来区分不同的业务。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。

这篇关于Golang如何对cron进行二次封装实现指定时间执行定时任务的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155214

相关文章

mybatis用拦截器实现字段加解密全过程

《mybatis用拦截器实现字段加解密全过程》本文通过自定义注解和MyBatis拦截器实现敏感信息加密,处理Parameter和ResultSet,确保数据库存储安全且查询结果解密可用... 目录前言拦截器的使用总结前言根据公司业务需要,灵活对客户敏感信息进行加解密,这里采用myBATis拦截器进行简单实

java实现多数据源切换方式

《java实现多数据源切换方式》本文介绍实现多数据源切换的四步方法:导入依赖、配置文件、启动类注解、使用@DS标记mapper和服务层,通过注解实现数据源动态切换,适用于实际开发中的多数据源场景... 目录一、导入依赖二、配置文件三、在启动类上配置四、在需要切换数据源的类上、方法上使用@DS注解结论一、导入

SQLServer中生成雪花ID(Snowflake ID)的实现方法

《SQLServer中生成雪花ID(SnowflakeID)的实现方法》:本文主要介绍在SQLServer中生成雪花ID(SnowflakeID)的实现方法,文中通过示例代码介绍的非常详细,... 目录前言认识雪花ID雪花ID的核心特点雪花ID的结构(64位)雪花ID的优势雪花ID的局限性雪花ID的应用场景

Linux升级或者切换python版本实现方式

《Linux升级或者切换python版本实现方式》本文介绍在Ubuntu/Debian系统升级Python至3.11或更高版本的方法,通过查看版本列表并选择新版本进行全局修改,需注意自动与手动模式的选... 目录升级系统python版本 (适用于全局修改)对于Ubuntu/Debian系统安装后,验证Pyt

Python实现开根号的五种方式

《Python实现开根号的五种方式》在日常数据处理、数学计算甚至算法题中,开根号是一个高频操作,但你知道吗?Python中实现开根号的方式远不止一种!本文总结了5种常用方法,感兴趣的小伙伴跟着小编一起... 目录一、为什么需要多种开根号方式?二、5种开根号方式详解方法1:数学库 math.sqrt() ——

nginx配置错误日志的实现步骤

《nginx配置错误日志的实现步骤》配置nginx代理过程中,如果出现错误,需要看日志,可以把nginx日志配置出来,以便快速定位日志问题,下面就来介绍一下nginx配置错误日志的实现步骤,感兴趣的可... 目录前言nginx配置错误日志总结前言在配置nginx代理过程中,如果出现错误,需要看日志,可以把

MySQL中DATE_FORMAT时间函数的使用小结

《MySQL中DATE_FORMAT时间函数的使用小结》本文主要介绍了MySQL中DATE_FORMAT时间函数的使用小结,用于格式化日期/时间字段,可提取年月、统计月份数据、精确到天,对大家的学习或... 目录前言DATE_FORMAT时间函数总结前言mysql可以使用DATE_FORMAT获取日期字段

Qt中实现多线程导出数据功能的四种方式小结

《Qt中实现多线程导出数据功能的四种方式小结》在以往的项目开发中,在很多地方用到了多线程,本文将记录下在Qt开发中用到的多线程技术实现方法,以导出指定范围的数字到txt文件为例,展示多线程不同的实现方... 目录前言导出文件的示例工具类QThreadQObject的moveToThread方法实现多线程QC

Go语言使用sync.Mutex实现资源加锁

《Go语言使用sync.Mutex实现资源加锁》数据共享是一把双刃剑,Go语言为我们提供了sync.Mutex,一种最基础也是最常用的加锁方式,用于保证在任意时刻只有一个goroutine能访问共享... 目录一、什么是 Mutex二、为什么需要加锁三、实战案例:并发安全的计数器1. 未加锁示例(存在竞态)

基于Redisson实现分布式系统下的接口限流

《基于Redisson实现分布式系统下的接口限流》在高并发场景下,接口限流是保障系统稳定性的重要手段,本文将介绍利用Redisson结合Redis实现分布式环境下的接口限流,具有一定的参考价值,感兴趣... 目录分布式限流的核心挑战基于 Redisson 的分布式限流设计思路实现步骤引入依赖定义限流注解实现