Cron


今天看到Cron,发现他源码比较少,就尝试着看一看

robfig/cron: a cron library for go (github.com)

什么是Cron表达式

Cron

虽然源码中跟这个表达式关系不是很深入,但是还是得了解一下==Cron表达式==

==| 秒 | 分 | 时 | 日 | 月 | 周 | 年(可选) |==

在大部分使用cron的场景下, - * / ? 这几个常用字符就可以满足我们的需求了。

  • ==【*】==:每的意思。在不同的字段上,就代表每秒,每分,每小时等。
  • ==【-】==:指定值的范围。比如[1-10],在秒字段里就是每分钟的第1到10秒,在分就是每小时的第1到10分钟,以此类推。
  • ==【,】==:指定某几个值。比如[2,4,5],在秒字段里就是每分钟的第2,第4,第5秒,以此类推。
  • ==【/】==:指定值的起始和增加幅度。比如[3/5],在秒字段就是每分钟的第3秒开始,每隔5秒生效一次,也就是第3秒、8秒、13秒,以此类推。
  • ==【?】==:仅用于【日】和【周】字段。因为在指定某日和周几的时候,这两个值实际上是冲突的,所以需要用【?】标识不生效的字段。比如【0 1 * * * ?】就代表每年每月每日每小时的1分0秒触发任务。这里的周就没有效果了。
cron表达式 含义 常用场景 执行时间
5 * * * * ? 每分钟的第5秒执行一次 常见的每分钟的定时任务,检查数据库和缓存数据是否一致 2021-04-11 13:10:05 2021-04-11 13:11:052021-04-11 13:12:052021-04-11 13:13:052021-04-11 13:14:052021-04-11 13:15:05
5 * 10-22 * * ? 从早上10点到晚上十点,每分钟的第5秒执行一次 将定时任务限制在每天的工作时间 2021-04-11 13:10:05 2021-04-11 13:11:052021-04-11 13:12:052021-04-11 13:13:052021-04-11 13:14:052021-04-11 13:15:05
5 0 0/6 * * ? 等效于5 0 0,6,12,18 * * ? 每天从0点开始,每隔6小时执行一次。执行时间为第0分5秒。 常用于每天较低频次的批量同步数据 2021-04-12 00:00:05 2021-04-12 06:00:052021-04-12 12:00:052021-04-12 18:00:05

源码随便看看


Cron解析

结构

Schedule

Schedule 表示下一次run的时间

1
2
3
4
5
6
// Schedule describes a job's duty cycle.
type Schedule interface {
// Next returns the next activation time, later than the given time.
// Next is invoked initially, and then each time the job is run.
Next(time.Time) time.Time
}

Parser

解析器,可以自定义parser,如果不自定义就是使用默认

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ScheduleParser is an interface for schedule spec parsers that return a Schedule
type ScheduleParser interface {
Parse(spec string) (Schedule, error)
}

// 具体的parse, it is custom
type ParseOption int

const (
Second ParseOption = 1 << iota // Seconds field, default 0
SecondOptional // Optional seconds field, default 0
Minute // Minutes field, default 0
Hour // Hours field, default 0
Dom // Day of month field, default *
Month // Month field, default *
Dow // Day of week field, default *
DowOptional // Optional day of week field, default *
Descriptor // Allow descriptors such as @monthly, @weekly, etc.
)

type Parser struct {
options ParseOption
}
-----------------------------------------------------------------------
// 但不定制parser的时候就是默认
var standardParser = NewParser(
Minute | Hour | Dom | Month | Dow | Descriptor,
)
// 解析一下
func ParseStandard(standardSpec string) (Schedule, error) {
return standardParser.Parse(standardSpec)
}
// 同时在 parse.go中可以重新 New 一个 custom parser
func NewParser(options ParseOption) Parser {
optionals := 0
if options&DowOptional > 0 {
optionals++
}
if options&SecondOptional > 0 {
optionals++
}
if optionals > 1 {
panic("multiple optionals may not be configured")
}
return Parser{options}
}
// 也可以将一个string -> schedule
func (p Parser) Parse(spec string) (Schedule, error){}

Job

作业,it is for submmitted cron jobs

这里的Job是一个接口,==FuncJob==是一个函数类型,说白了就是一个函数

1
2
3
4
5
6
type Job interface {
Run()
}

type FuncJob func()
func (f FuncJob) Run() { f() }

Option

可以认为是配置,可以来配置==Cron==,一种配置函数,可以将其他类型转化为==Cron==类型

1
2
3
4
5
6
7
type Option func(*Cron)
// 就是一些配置的转化
func WithParser(p ScheduleParser) Option
func WithChain(wrappers ...JobWrapper) Option
func WithSeconds() Option
func WithLogger(logger Logger) Option
func WithLocation(loc *time.Location) Option

Entry

Entry 是添加到Cron的一次封装,每个Entry有一个ID

1
2
3
4
5
6
7
8
9
10
type EntryID int

type Entry struct {
ID EntryID // Entry独特的ID
Schedule Schedule // 时间,表示这个job什么时候run
Next time.Time // 作业下次运行的时间
Prev time.Time // 作业上次运行的时间
WrappedJob Job
Job Job // 两个Job都是函数接口类型的,上面的传入Chain中进行包装
}

Chain

Chain结构体则用来存储一系列的JobWrapper装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type JobWrapper func(Job) Job


type Chain struct {
wrappers []JobWrapper
}

func NewChain(c ...JobWrapper) Chain {return Chain{c}}

func (c Chain) Then(j Job) Job {
for i := range c.wrappers {
j = c.wrappers[len(c.wrappers)-i-1](j)
}
return j
}
// 通过调用Then方法,你可以将这个链表中的所有装饰器依次应用到某个特定的Job对象上。

Cron(核心)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Cron struct {
entries []*Entry // 保存了所有加入到 Cron 的作业
chain Chain
stop chan struct{} // 接收 Stop() 信号的 chan
add chan *Entry // Cron 运行过程中接收 AddJob() 信号的 chan
remove chan EntryID // 接收移除 Job 信号的 chan
snapshot chan chan []Entry // 快照信号
running bool // 标志 Cron 是否在运行中
logger Logger
runningMu sync.Mutex // Cron 运行前需要抢占该锁,保证并发安全
location *time.Location
parser ScheduleParser // cron 表达式的解析器
nextID EntryID // 即将加入的 Job 对应的 Entry 的 ID
jobWaiter sync.WaitGroup
}

New()

就是得到一个新的==Cron==实例,同时使用==Opts==来配置实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func New(opts ...Option) *Cron {
c := &Cron{
entries: nil,
chain: NewChain(),
add: make(chan *Entry),
stop: make(chan struct{}),
snapshot: make(chan chan []Entry),
remove: make(chan EntryID),
running: false,
runningMu: sync.Mutex{},
logger: DefaultLogger,
location: time.Local,
parser: standardParser,
}
for _, opt := range opts {
opt(c)
}
return c
}

AddFunc()

进行了一次包装,同时使用==c.parser.Parse(spec)==对字符串到==schedule==的转换

1
2
3
4
5
6
7
8
9
10
11
12
func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
return c.AddJob(spec, FuncJob(cmd))
}

func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) {
schedule, err := c.parser.Parse(spec)
if err != nil {
return 0, err
}
return c.Schedule(schedule, cmd), nil
}

Run()/Start()/stop()

这里有点没看懂==

总结

  1. 其他文章中说==Cron==库设计的特别好,我没看出来,我太菜了,但是也算是看明白了一些东西,关于通道的某些知识可能还是看不太懂。
  2. ==Option==的认知,以前一直不知道==Option==是来干什么的,并且总发现每个依赖基本上都有一个Option
  3. 装饰器的使用,通过装饰器进行cumtom
  4. 以后多看看。