Go cron定时任务的用法
cron是什么
cron的意思就是:计划任务,说白了就是定时任务。我和系统约个时间,你在几点几分几秒或者每隔几分钟跑一个任务(job),就那么简单。
cron表达式
cron表达式是一个好东西,这个东西不仅Java的quartZ能用到,Go语言中也可以用到。我没有用过Linux的cron,但网上说Linux也是可以用crontab -e 命令来配置定时任务。Go语言和Java中都是可以精确到秒的,但是Linux中不行。
cron表达式代表一个时间的集合,使用6个空格分隔的字段表示:
字段名 | 是否必须 | 允许的值 | 允许的特定字符 |
秒(Seconds) | 是 | 0-59 | * / , - |
分(Minute) | 是 | 0-59 | * / , - |
时(Hours) | 是 | 0-23 | * / , - |
日(Day of month) | 是 | 1-31 | * / , - ? |
月(Month) | 是 | 1-12 或 JAN-DEC | * / , - |
星期(Day of week) | 否 | 0-6 或 SUM-SAT | * / , - ? |
注:
1.月(Month)和星期(Day of week)字段的值不区分大小写,如:SUN、Sun 和 sun 是一样的。
2.星期(Day of week)字段如果没提供,相当于是 *
# ┌───────────── min (0 - 59)
# │ ┌────────────── hour (0 - 23)
# │ │ ┌─────────────── day of month (1 - 31)
# │ │ │ ┌──────────────── month (1 - 12)
# │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to
# │ │ │ │ │ Saturday, or use names; 7 is also Sunday)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * command to execute
cron特定字符说明
1)星号(*)
表示 cron 表达式能匹配该字段的所有值。如在第5个字段使用星号(month),表示每个月
2)斜线(/)
表示增长间隔,如第1个字段(minutes) 值是 3-59/15,表示每小时的第3分钟开始执行一次,之后每隔 15 分钟执行一次(即 3、18、33、48 这些时间点执行),这里也可以表示为:3/15
3)逗号(,)
用于枚举值,如第6个字段值是 MON,WED,FRI,表示 星期一、三、五 执行
4)连字号(-)
表示一个范围,如第3个字段的值为 9-17 表示 9am 到 5pm 直接每个小时(包括9和17)
5)问号(?)
只用于 日(Day of month) 和 星期(Day of week),表示不指定值,可以用于代替 *
6)L,W,#
Go中没有L,W,#的用法,下文作解释。
cron举例说明
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
下载安装
控制台输入 go get github.com/robfig/cron 去下载定时任务的Go包,前提是你的 $GOPATH 已经配置好
源码解析
文件目录讲解
constantdelay.go #一个最简单的秒级别定时系统。与cron无关
constantdelay_test.go #测试
cron.go #Cron系统。管理一系列的cron定时任务(Schedule Job)
cron_test.go #测试
doc.go #说明文档
LICENSE #授权书
parser.go #解析器,解析cron格式字符串城一个具体的定时器(Schedule)
parser_test.go #测试
README.md #README
spec.go #单个定时器(Schedule)结构体。如何计算自己的下一次触发时间
spec_test.go #测试
cron.go
结构体:
// 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.
// Cron保持任意数量的条目的轨道,调用相关的func时间表指定。它可以被启动,停止和条目,可运行的同时进行检查。
type Cron struct {
entries []*Entry // 任务
stop chan struct{} // 叫停止的途径
add chan *Entry // 添加新任务的方式
snapshot chan []*Entry // 请求获取任务快照的方式
running bool // 是否在运行
ErrorLog *log.Logger // 出错日志(新增属性)
location *time.Location // 所在地区(新增属性)
}
// Entry consists of a schedule and the func to execute on that schedule.
// 入口包括时间表和可在时间表上执行的func
type Entry struct {
// 计时器
Schedule Schedule
// 下次执行时间
Next time.Time
// 上次执行时间
Prev time.Time
// 任务
Job Job
}
关键方法:
// 开始任务
// 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 = true
go c.run()
}
// 结束任务
// Stop stops the cron scheduler if it is running; otherwise it does nothing.
func (c *Cron) Stop() {
if !c.running {
return
}
c.stop <- struct{}{}
c.running = false
} // 执行定时任务
// 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 := time.Now().In(c.location)
for _, entry := range c.entries {
entry.Next = entry.Schedule.Next(now)
}
// 无限循环
for {
//通过对下一个执行时间进行排序,判断那些任务是下一次被执行的,防在队列的前面.sort是用来做排序的
sort.Sort(byTime(c.entries)) var effective time.Time
if len(c.entries) == || c.entries[].Next.IsZero() {
// If there are no entries yet, just sleep - it still handles new entries
// and stop requests.
effective = now.AddDate(, , )
} else {
effective = c.entries[].Next
} timer := time.NewTimer(effective.Sub(now))
select {
case now = <-timer.C: // 执行当前任务
now = now.In(c.location)
// Run every entry whose next time was this effective time.
for _, e := range c.entries {
if e.Next != effective {
break
}
go c.runWithRecovery(e.Job)
e.Prev = e.Next
e.Next = e.Schedule.Next(now)
}
continue case newEntry := <-c.add: // 添加新的任务
c.entries = append(c.entries, newEntry)
newEntry.Next = newEntry.Schedule.Next(time.Now().In(c.location)) case <-c.snapshot: // 获取快照
c.snapshot <- c.entrySnapshot() case <-c.stop: // 停止任务
timer.Stop()
return
} // 'now' should be updated after newEntry and snapshot cases.
now = time.Now().In(c.location)
timer.Stop()
}
}
spec.go
结构体及关键方法:
// SpecSchedule specifies a duty cycle (to the second granularity), based on a
// traditional crontab specification. It is computed initially and stored as bit sets.
type SpecSchedule struct {
// 表达式中锁表明的,秒,分,时,日,月,周,每个都是uint64
// Dom:Day of Month,Dow:Day of week
Second, Minute, Hour, Dom, Month, Dow uint64
} // bounds provides a range of acceptable values (plus a map of name to value).
// 定义了表达式的结构体
type bounds struct {
min, max uint
names map[string]uint
} // The bounds for each field.
// 这样就能看出各个表达式的范围
var (
seconds = bounds{, , nil}
minutes = bounds{, , nil}
hours = bounds{, , nil}
dom = bounds{, , nil}
months = bounds{, , map[string]uint{
"jan": ,
"feb": ,
"mar": ,
"apr": ,
"may": ,
"jun": ,
"jul": ,
"aug": ,
"sep": ,
"oct": ,
"nov": ,
"dec": ,
}}
dow = bounds{, , map[string]uint{
"sun": ,
"mon": ,
"tue": ,
"wed": ,
"thu": ,
"fri": ,
"sat": ,
}}
) const (
// Set the top bit if a star was included in the expression.
starBit = <<
)
看了上面的东西肯定有人疑惑为什么秒分时这些都是定义了unit64,以及定义了一个常量starBit = 1 << 63这种写法,这是逻辑运算符。表示二进制1向左移动63位。原因如下:
cron表达式是用来表示一系列时间的,而时间是无法逃脱自己的区间的 , 分,秒 0 - 59 , 时 0 - 23 , 天/月 0 - 31 , 天/周 0 - 6 , 月0 - 11 。 这些本质上都是一个点集合,或者说是一个整数区间。 那么对于任意的整数区间 , 可以描述cron的如下部分规则。
* | ?
任意 , 对应区间上的所有点。 ( 额外注意 日/周 , 日 / 月 的相互干扰。)- 纯数字 , 对应一个具体的点。
/
分割的两个数字 a , b, 区间上符合 a + n * b 的所有点 ( n >= 0 )。-
分割的两个数字, 对应这两个数字决定的区间内的所有点。L | W
需要对于特定的时间特殊判断, 无法通用的对应到区间上的点。
至此, robfig/cron
为什么不支持 L | W
的原因已经明了了。去除这两条规则后, 其余的规则其实完全可以使用点的穷举来通用表示。 考虑到最大的区间也不过是60个点,那么使用一个uint64
的整数的每一位来表示一个点便很合适了。所以定义unit64不为过
下面是go中cron表达式的方法:
/*
------------------------------------------------------------
第64位标记任意 , 用于 日/周 , 日 / 月 的相互干扰。
63 - 0 为 表示区间 [63 , 0] 的 每一个点。
------------------------------------------------------------ 假设区间是 0 - 63 , 则有如下的例子 : 比如 0/3 的表示如下 : (表示每隔两位为1)
* / ?
+---+--------------------------------------------------------+
| 0 | 1 0 0 1 0 0 1 ~~ ~~ 1 0 0 1 0 0 1 |
+---+--------------------------------------------------------+
63 ~ ~ ~~ 0 比如 2-5 的表示如下 : (表示从右往左2-5位上都是1)
* / ?
+---+--------------------------------------------------------+
| 0 | 0 0 0 0 ~ ~ ~~ ~ 0 0 0 1 1 1 1 0 0 |
+---+--------------------------------------------------------+
63 ~ ~ ~~ 0 比如 * 的表示如下 : (表示所有位置上都为1)
* / ?
+---+--------------------------------------------------------+
| 1 | 1 1 1 1 1 ~ ~ ~ 1 1 1 1 1 1 1 1 1 |
+---+--------------------------------------------------------+
63 ~ ~ ~~ 0
*/
parser.go
将字符串解析为SpecSchedule的类。
package cron import (
"fmt"
"math"
"strconv"
"strings"
"time"
) // Configuration options for creating a parser. Most options specify which
// fields should be included, while others enable features. If a field is not
// included the parser will assume a default value. These options do not change
// the order fields are parse in.
type ParseOption int const (
Second ParseOption = << iota // 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.
) var places = []ParseOption{
Second,
Minute,
Hour,
Dom,
Month,
Dow,
} var defaults = []string{
"",
"",
"",
"*",
"*",
"*",
} // A custom Parser that can be configured.
type Parser struct {
options ParseOption
optionals int
} // Creates a custom Parser with custom options.
//
// // Standard parser without descriptors
// specParser := NewParser(Minute | Hour | Dom | Month | Dow)
// sched, err := specParser.Parse("0 0 15 */3 *")
//
// // Same as above, just excludes time fields
// subsParser := NewParser(Dom | Month | Dow)
// sched, err := specParser.Parse("15 */3 *")
//
// // Same as above, just makes Dow optional
// subsParser := NewParser(Dom | Month | DowOptional)
// sched, err := specParser.Parse("15 */3")
//
func NewParser(options ParseOption) Parser {
optionals :=
if options&DowOptional > {
options |= Dow
optionals++
}
return Parser{options, optionals}
} // Parse returns a new crontab schedule representing the given spec.
// It returns a descriptive error if the spec is not valid.
// It accepts crontab specs and features configured by NewParser.
// 将字符串解析成为SpecSchedule 。 SpecSchedule符合Schedule接口 func (p Parser) Parse(spec string) (Schedule, error) {
// 直接处理特殊的特殊的字符串
if spec[] == '@' && p.options&Descriptor > {
return parseDescriptor(spec)
} // Figure out how many fields we need
max :=
for _, place := range places {
if p.options&place > {
max++
}
}
min := max - p.optionals // cron利用空白拆解出独立的items。
fields := strings.Fields(spec) // 验证表达式取值范围
if count := len(fields); count < min || count > max {
if min == max {
return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)
}
return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)
} // Fill in missing fields
fields = expandFields(fields, p.options) var err error
field := func(field string, r bounds) uint64 {
if err != nil {
return
}
var bits uint64
bits, err = getField(field, r)
return bits
} var (
second = field(fields[], seconds)
minute = field(fields[], minutes)
hour = field(fields[], hours)
dayofmonth = field(fields[], dom)
month = field(fields[], months)
dayofweek = field(fields[], dow)
)
if err != nil {
return nil, err
}
// 返回所需要的SpecSchedule
return &SpecSchedule{
Second: second,
Minute: minute,
Hour: hour,
Dom: dayofmonth,
Month: month,
Dow: dayofweek,
}, nil
} func expandFields(fields []string, options ParseOption) []string {
n :=
count := len(fields)
expFields := make([]string, len(places))
copy(expFields, defaults)
for i, place := range places {
if options&place > {
expFields[i] = fields[n]
n++
}
if n == count {
break
}
}
return expFields
} var standardParser = NewParser(
Minute | Hour | Dom | Month | Dow | Descriptor,
) // ParseStandard returns a new crontab schedule representing the given standardSpec
// (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always
// pass 5 entries representing: minute, hour, day of month, month and day of week,
// in that order. It returns a descriptive error if the spec is not valid.
//
// It accepts
// - Standard crontab specs, e.g. "* * * * ?"
// - Descriptors, e.g. "@midnight", "@every 1h30m"
// 这里表示不仅可以使用cron表达式,也可以使用@midnight @every等方法 func ParseStandard(standardSpec string) (Schedule, error) {
return standardParser.Parse(standardSpec)
} var defaultParser = NewParser(
Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,
) // Parse returns a new crontab schedule representing the given spec.
// It returns a descriptive error if the spec is not valid.
//
// It accepts
// - Full crontab specs, e.g. "* * * * * ?"
// - Descriptors, e.g. "@midnight", "@every 1h30m"
func Parse(spec string) (Schedule, error) {
return defaultParser.Parse(spec)
} // getField returns an Int with the bits set representing all of the times that
// the field represents or error parsing field value. A "field" is a comma-separated
// list of "ranges".
func getField(field string, r bounds) (uint64, error) {
var bits uint64
ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
for _, expr := range ranges {
bit, err := getRange(expr, r)
if err != nil {
return bits, err
}
bits |= bit
}
return bits, nil
} // getRange returns the bits indicated by the given expression:
// number | number "-" number [ "/" number ]
// or error parsing range.
func getRange(expr string, r bounds) (uint64, error) {
var (
start, end, step uint
rangeAndStep = strings.Split(expr, "/")
lowAndHigh = strings.Split(rangeAndStep[], "-")
singleDigit = len(lowAndHigh) ==
err error
) var extra uint64
if lowAndHigh[] == "*" || lowAndHigh[] == "?" {
start = r.min
end = r.max
extra = starBit
} else {
start, err = parseIntOrName(lowAndHigh[], r.names)
if err != nil {
return , err
}
switch len(lowAndHigh) {
case :
end = start
case :
end, err = parseIntOrName(lowAndHigh[], r.names)
if err != nil {
return , err
}
default:
return , fmt.Errorf("Too many hyphens: %s", expr)
}
} switch len(rangeAndStep) {
case :
step =
case :
step, err = mustParseInt(rangeAndStep[])
if err != nil {
return , err
} // Special handling: "N/step" means "N-max/step".
if singleDigit {
end = r.max
}
default:
return , fmt.Errorf("Too many slashes: %s", expr)
} if start < r.min {
return , fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
}
if end > r.max {
return , fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
}
if start > end {
return , fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
}
if step == {
return , fmt.Errorf("Step of range should be a positive number: %s", expr)
} return getBits(start, end, step) | extra, nil
} // parseIntOrName returns the (possibly-named) integer contained in expr.
func parseIntOrName(expr string, names map[string]uint) (uint, error) {
if names != nil {
if namedInt, ok := names[strings.ToLower(expr)]; ok {
return namedInt, nil
}
}
return mustParseInt(expr)
} // mustParseInt parses the given expression as an int or returns an error.
func mustParseInt(expr string) (uint, error) {
num, err := strconv.Atoi(expr)
if err != nil {
return , fmt.Errorf("Failed to parse int from %s: %s", expr, err)
}
if num < {
return , fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)
} return uint(num), nil
} // getBits sets all bits in the range [min, max], modulo the given step size.
func getBits(min, max, step uint) uint64 {
var bits uint64 // If step is 1, use shifts.
if step == {
return ^(math.MaxUint64 << (max + )) & (math.MaxUint64 << min)
} // Else, use a simple loop.
for i := min; i <= max; i += step {
bits |= << i
}
return bits
} // all returns all bits within the given bounds. (plus the star bit)
func all(r bounds) uint64 {
return getBits(r.min, r.max, ) | starBit
} // parseDescriptor returns a predefined schedule for the expression, or error if none matches.
func parseDescriptor(descriptor string) (Schedule, error) {
switch descriptor {
case "@yearly", "@annually":
return &SpecSchedule{
Second: << seconds.min,
Minute: << minutes.min,
Hour: << hours.min,
Dom: << dom.min,
Month: << months.min,
Dow: all(dow),
}, nil case "@monthly":
return &SpecSchedule{
Second: << seconds.min,
Minute: << minutes.min,
Hour: << hours.min,
Dom: << dom.min,
Month: all(months),
Dow: all(dow),
}, nil case "@weekly":
return &SpecSchedule{
Second: << seconds.min,
Minute: << minutes.min,
Hour: << hours.min,
Dom: all(dom),
Month: all(months),
Dow: << dow.min,
}, nil case "@daily", "@midnight":
return &SpecSchedule{
Second: << seconds.min,
Minute: << minutes.min,
Hour: << hours.min,
Dom: all(dom),
Month: all(months),
Dow: all(dow),
}, nil case "@hourly":
return &SpecSchedule{
Second: << seconds.min,
Minute: << minutes.min,
Hour: all(hours),
Dom: all(dom),
Month: all(months),
Dow: all(dow),
}, nil
} const every = "@every "
if strings.HasPrefix(descriptor, every) {
duration, err := time.ParseDuration(descriptor[len(every):])
if err != nil {
return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)
}
return Every(duration), nil
} return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)
}
项目中应用
package main import (
"github.com/robfig/cron"
"log"
) func main() {
i := 0
c := cron.New()
spec := "*/5 * * * * ?"
c.AddFunc(spec, func() {
i++
log.Println("cron running:", i)
})
c.AddFunc("@every 1h1m", func() {
i++
log.Println("cron running:", i)
})
c.Start()
}
注: @every 用法比较特殊,这是Go里面比较特色的用法。同样的还有 @yearly @annually @monthly @weekly @daily @midnight @hourly 这里面就不一一赘述了。希望大家能够自己探索。
参考网站:
http://blog.studygolang.com/2014/02/go_crontab/
http://blog.csdn.net/cchd0001/article/details/51076922
https://en.wikipedia.org/wiki/Cron
https://github.com/robfig/cron
Go cron定时任务的用法的更多相关文章
- cron表达式的用法 【比较全面靠谱】
转: cron表达式的用法 cron表达式通过特定的规则指定时间,用于定时任务,本文简单记录它的部分语法和实例,并不完全,能覆盖日常大部分需求. 1. 整体结构 cron表达式是一个字符串,分为6或7 ...
- 使用 cron 定时任务实现 war 自动化发布
autoRelease.sh #!/bin/sh /home/tomcat/bin/shutdown.sh echo "tomcat stoped" cd /home/tomcat ...
- cron定时任务介绍
什么是cron? Cron是linux系统中用来定期执行或指定程序任务的一种服务或软件.与它相关的有两个工具:crond 和 crontab.crond 就是 cron 在系统内的宿主程序,cront ...
- linux ,cron定时任务 备份mysql数据库
cron 定时任务执行备份脚本文件 backup.sh #!/bin/bash USER="root" PASSWORD="xxxxx" DATABASE=&q ...
- 珠峰培训node 珠峰爬虫| cron 定时任务
1.cron 定时任务 CronJob var CronJob = require('cron').CronJob; // 秒 分钟 时 天
- Cron定时任务应用到Thinkphp – 贤生博客
Cron定时任务应用到Thinkphp 安装crontab: yum install crontabs 关于cron的一些命令: /sbin/service crond start //启动服务 /s ...
- linux下的cron定时任务知识梳理
1 cron定时任务 1.1 cron介绍 为什么需要cron定时任务? 1)cron服务在安装完Linux系统后就默认就存在,主要用来定期执行命令或定期执行指定的应用程序; 2)cron服务默认情况 ...
- .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件
常用的定时任务组件有 Quartz.Net 和 Hangfire 两种,这两种是使用人数比较多的定时任务组件,个人以前也是使用的 Hangfire ,慢慢的发现自己想要的其实只是一个能够根据 Cron ...
- .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版)
在上个月写过一篇 .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 的文章,当时 CronSchedule 的实现是使用了,每个服务都独立进入到一个 while 循环中,进行定期扫描是否 ...
随机推荐
- NSInvocation
NSInvocation 基本简介 NSInvocation是一个静态描绘的OC消息,也就是说,它是一个动作,这个动作可以变成一个对象.NSInvocation对象在对象和对象之间和应用程序和应用程序 ...
- 【读书笔记】iOS-Tagged Pointer对象-注意事项
一,2013年9月,苹果推出了iPhone5s,与此同时,iPhone5s配备了首个采用64位架构的A7双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念. 对于64 ...
- 弃用的同步get和post请求
#import "ViewController.h" #import "Header.h" @interface ViewController () <N ...
- OC--代理模式
一,代理设计模式的场合: 当对象A发生了一些行为,想告知对象B (让对象B成为对象A的代理对象) 对象B想监听对象A的一些行为 (让对象B成为对象A的代理对象) 当对象A无法处理某些行为的时候,想让对 ...
- 如何获取QQ里的截图app?
电脑系统平台:OS X EI Capitan 10.11 在以前的旧的QQ版本,QQ的截图的偏好还有一个开机自启动的选项: 现在新的版本,却没有了"开机自动运行"的选项,然而有时候 ...
- Swift基础之闭包
内容纲要: 1.闭包基础 2.关于闭包循环引用 正文: 1.闭包 闭包是自包含的函数代码块,可以在代码中被传递和使用.Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks) ...
- 关于touch事件对于性能的影响
第一次写博客随笔,废话不多说,直接进入正题. 最近一直专注于移动终端的开发,碰到了一个比较棘手的事情,就是touch事件,大家都知道,touch事件有几种,无非就是touchstart,touchmo ...
- DEV主从表
1.主从表隐藏表格展开按钮. 当主表内容不包含子表时候隐藏,主从表加号图标.效果如下图. 实现代码 private void gvMain_CustomDrawCell(object sender, ...
- win7开启休眠功能
win7有的系统默认关机选项没有休眠功能,其实是没打开. cmd-> powercfg -hibernate on 即可
- Linux下恢复删除的文件
转自:http://github.tiankonguse.com/blog/2015/09/13/linux-remove-recovery/ 下午, DBA找我说有些SQL执行了两个小时了,导致主从 ...