robfig/cron-go cron定时任务库架构剖析
创始人
2024-03-07 23:34:21
0

Cron深度解析


思想

对于cron 这个三方库来说,他可以说是做两件事,其一是:解析cron string,生成一个定时器,达到循环时间发送信号。其二是核心(引擎):用以执行,判断,接收任务,停止cron等事情。

核心思想是面向接口,抽象开发。只有抽象才能使得哪怕九年时间过去,依然是这么简洁,普及。因为脱离了现实世界到达了柏拉图心中的理念世界理想国。

而为什么接口的实现是只要继承接口方法而不是保持对象信息呢?柏拉图的理想国认为现实世界的一切都是理念世界中各自本体的投影。那本体有哪些特征可以被用来在现实世界中与对象做判断呢?这里就涉及到唯心唯物了。主观唯心认为我与世界交互所以有了世界,如王阳明,笛卡尔,康德,佛教教义。把他们划到主观唯心也是不太对的,因为或许这就是真相,但却用两个带有偏见的名词对其做概括。之所以说主观唯心,一句诗概括就是:你未看此花时,此花与汝心同归于寂:你来看此花时,则此花颜色一时明白起来:便知此花不在你的心外。就像判断结构体是否实现接口就看是否实现了动作!因为只有动词 表示的是我在与世界做交互,互动所以有了世界。


robfig/cron at v3.0.1架构图

robfig/cron 架构图


关键位置分析

Cron 实现 Executable 接口

Executable 接口 有三个方法,Cron结构体继承。

  • 通过Start 会 启动一个并行队列
  • 通过Run 会启动一个串行队列
// Start the cron scheduler in its own go-routine, or no-op if already started.
func (c *Cron) Start() {if c.running {return}c.running = truego c.run()
}// Run the cron scheduler, or no-op if already running.
func (c *Cron) Run() {if c.running {return}c.running = truec.run()
}

Cron 结构体分析

// 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.
type Cron struct {entries  []*Entrystop     chan struct{}add      chan *Entrysnapshot chan []*Entryrunning  boolErrorLog *log.Loggerlocation *time.Location
}
  • entries:指针切片,用以执行当中的entry(即每一个任务)以及接收传进来的任务
  • stop:停止信号
  • add:接收传输管道
  • snapshot:entries 副本,打印需要

cron string 解析

在 AddJob 方法中,通过Parse 函数获取实现了Schedule 接口的结构体:SpecSchedule

func parseDescriptor(descriptor string) (Schedule, error) {switch descriptor {case "@yearly", "@annually":return &SpecSchedule{Second: 1 << seconds.min,Minute: 1 << minutes.min,Hour:   1 << hours.min,Dom:    1 << dom.min,Month:  1 << months.min,Dow:    all(dow),}, nilcase "@monthly":return &SpecSchedule{Second: 1 << seconds.min,Minute: 1 << minutes.min,Hour:   1 << hours.min,Dom:    1 << dom.min,Month:  all(months),Dow:    all(dow),}, nil
......
}// 该结构体实现了 Schedule 接口
type SpecSchedule struct {Second, Minute, Hour, Dom, Month, Dow uint64
}

引擎解析

// Run the scheduler. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) run() {// Figure out the next activation times for each entry.now := c.now()for _, entry := range c.entries {entry.Next = entry.Schedule.Next(now)}for {// Determine the next entry to run.sort.Sort(byTime(c.entries))var timer *time.Timerif len(c.entries) == 0 || c.entries[0].Next.IsZero() {// If there are no entries yet, just sleep - it still handles new entries// and stop requests.timer = time.NewTimer(100000 * time.Hour)} else {timer = time.NewTimer(c.entries[0].Next.Sub(now))}for {select {case now = <-timer.C:now = now.In(c.location)// Run every entry whose next time was less than nowfor _, e := range c.entries {if e.Next.After(now) || e.Next.IsZero() {break}go c.runWithRecovery(e.Job)e.Prev = e.Nexte.Next = e.Schedule.Next(now)}case newEntry := <-c.add:timer.Stop()now = c.now()newEntry.Next = newEntry.Schedule.Next(now)c.entries = append(c.entries, newEntry)case <-c.snapshot:c.snapshot <- c.entrySnapshot()continuecase <-c.stop:timer.Stop()return}break}}
}

就是一个循环,循环等,循环加。

改进点

可以发现在这其中并没有写注销某一个定时任务的方法

但是想实现也是比较简单的,加一个case,监听channel,遍历 entries,找到目标,删除掉就ok了

但是既然它不加,应该是有原因的,不加的原因应该是处于经验考虑的吧?

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...