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定时任务的用法的更多相关文章

  1. cron表达式的用法 【比较全面靠谱】

    转: cron表达式的用法 cron表达式通过特定的规则指定时间,用于定时任务,本文简单记录它的部分语法和实例,并不完全,能覆盖日常大部分需求. 1. 整体结构 cron表达式是一个字符串,分为6或7 ...

  2. 使用 cron 定时任务实现 war 自动化发布

    autoRelease.sh #!/bin/sh /home/tomcat/bin/shutdown.sh echo "tomcat stoped" cd /home/tomcat ...

  3. cron定时任务介绍

    什么是cron? Cron是linux系统中用来定期执行或指定程序任务的一种服务或软件.与它相关的有两个工具:crond 和 crontab.crond 就是 cron 在系统内的宿主程序,cront ...

  4. linux ,cron定时任务 备份mysql数据库

    cron 定时任务执行备份脚本文件 backup.sh #!/bin/bash USER="root" PASSWORD="xxxxx" DATABASE=&q ...

  5. 珠峰培训node 珠峰爬虫| cron 定时任务

    1.cron 定时任务 CronJob var CronJob = require('cron').CronJob; // 秒 分钟 时 天

  6. Cron定时任务应用到Thinkphp – 贤生博客

    Cron定时任务应用到Thinkphp 安装crontab: yum install crontabs 关于cron的一些命令: /sbin/service crond start //启动服务 /s ...

  7. linux下的cron定时任务知识梳理

    1 cron定时任务 1.1 cron介绍 为什么需要cron定时任务? 1)cron服务在安装完Linux系统后就默认就存在,主要用来定期执行命令或定期执行指定的应用程序; 2)cron服务默认情况 ...

  8. .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件

    常用的定时任务组件有 Quartz.Net 和 Hangfire 两种,这两种是使用人数比较多的定时任务组件,个人以前也是使用的 Hangfire ,慢慢的发现自己想要的其实只是一个能够根据 Cron ...

  9. .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 (Timer 优化版)

    在上个月写过一篇 .NET 纯原生实现 Cron 定时任务执行,未依赖第三方组件 的文章,当时 CronSchedule 的实现是使用了,每个服务都独立进入到一个 while 循环中,进行定期扫描是否 ...

随机推荐

  1. IOS 简单动画 首尾式动画

    首尾式动画 首尾式动画即通过实现控件由初始状态到结束状态的过程.(主要表现在控件的Frame 透明度 ) // // ViewController.m // CX 简单动画 // // Created ...

  2. 写给IOS开发工程师的网页前端入门笔记

    前言:作为IOS开发工程师,终会接触到网页前端开发,甚至可能会有 用HTML5开发IOS的app客户端的需求.比如现在上架的app就有比如理财类型的app有的就用HTML开发的,从理财类型的app需求 ...

  3. LINQ SQL分组取最近一条记录

    最近项目有一个需求,从订单表查询出每个客户最近一条订单记录.数据库表结构如下图 SELECT * FROM ( select ROW_NUMBER()over(partition by [custid ...

  4. docker-1 初识docker

    五分钟认识docker 什么是docker? 把他想象成一个用了一种新颖方式实现的超轻量虚拟机,在大概效果上也是正确的.当然在实现的原理和应用上还是和VM有巨大差别的,并且专业的叫法是应用容器(App ...

  5. java使用httpcomponents post发送json数据

    一.适用场景 当我们向第三方系统提交数据的时候,需要调用第三方系统提供的接口.不同的系统提供的接口也不一样,有的是SOAP Webservice.RESTful Webservice 或其他的.当使用 ...

  6. Button未设type属性时在非IE6/7中具有submit特性

    代码如下 <!DOCTYPE html> <html> <head> <title>Button在Form中具有submit的特性</title& ...

  7. android去掉顶部标题栏

    在清单文件(manifest.xml)里面实现 <application> <activity android:name="cn.ui.activity.UserRegAc ...

  8. Python中的库使用之一 PIL

    先上代码:本文主要工给自己参考,在需要的时候直接搜索查找就行了,不想看没有实际运行例子的文档,当参考完这部分还哦未能解决问题在参考PIL的相关文档! Skip to content This repo ...

  9. nagios 自定义插件demo

    #!/bin/bash loadavg=$( uptime | awk -F: '{print $4}' | xargs ) load1int=$( ) load5int=$( ) load15int ...

  10. volatile与synchronized关键字

    volatile关键字相信了解Java多线程的读者都很清楚它的作用.volatile关键字用于声明简单类型变量,如int.float.boolean等数据类型.如果这些简单数据类型声明为volatil ...