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

Written by

in

文章目录
  • go中常用的定时任务库:https://github.com/robfig/cron,不支持指定某个时间开始执行一次任务,然后再以一定的时间间隔开始执行任务。 在真实的业务场景中可能会有这样的需求:进行某个操作时执行一次任务,然后根据这个操作时间每隔一段时间执行一次任务;再次操作时,立即执行一次任务,然后根据此次操作的时间点每隔一段时间执行一次任务,这时就需要停止上一次操作产生的定时间隔执行任务。 下面就根据go中的cron库进行封装来满足上述场景的功能。
  • go get -u github.com/robfig/cron/v3
  • 上面给出一个指定一个时间开始执行任务,然后再以一定的时间间隔定时执行任务的功能,可以根据业务场景的需求去修改上述功能,新增一些属性,比如除了动态修改定时任务时间,还可以动态修改时间间隔或执行函数,或者新增一个tag字段打印在日志中来区分不同的业务。 以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。 您可能感兴趣的文章: Golang定时任务框架GoCron的源码分析 详解如何使用Golang实现Cron定时任务 Golang实现CronJob(定时任务)的方法详解 golang定时任务cron项目实操指南 Golang cron 定时器和定时任务的使用场景 Golang Cron 定时任务的实现示例
  • 目录
    • 背景
    • cron库下载
    • 代码示例
      • 【1】结构体定义
      • 【2】定时任务开启
      • 【3】使用示例
      • 【4】控制台输出
    • 总结

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

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

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

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

      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 定时任务中执行的函数

      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.stop()
      
      	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.getInterval(), 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(); 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函数目的就是为了停止上一次的任务,保证不会有冲突的定时任务执行。

      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

      $ go run ./cron_demo/main.go
      ===== 指定开始时间在当前时间之前,会立即执行一次,然后定时执行 ======
      [2024-10-08 17:23:38.135] | INFO | 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字段打印在日志中来区分不同的业务。

      以上为个人经验,希望能给大家一个参考,也希望大家多多支持风君子博客。

      您可能感兴趣的文章:

      • Golang定时任务框架GoCron的源码分析
      • 详解如何使用Golang实现Cron定时任务
      • Golang实现CronJob(定时任务)的方法详解
      • golang定时任务cron项目实操指南
      • Golang cron 定时器和定时任务的使用场景
      • Golang Cron 定时任务的实现示例

      站内搜索