Go中的定时任务

来源:互联网 发布:打印机正在向windows 编辑:程序博客网 时间:2024/06/15 15:19

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 已经配置好

源码解析

  文件目录讲解 

复制代码
 1 constantdelay.go      #一个最简单的秒级别定时系统。与cron无关 2 constantdelay_test.go #测试 3 cron.go               #Cron系统。管理一系列的cron定时任务(Schedule Job) 4 cron_test.go          #测试 5 doc.go                #说明文档 6 LICENSE               #授权书  7 parser.go             #解析器,解析cron格式字符串城一个具体的定时器(Schedule) 8 parser_test.go        #测试 9 README.md             #README10 spec.go               #单个定时器(Schedule)结构体。如何计算自己的下一次触发时间11 spec_test.go          #测试
复制代码

  cron.go

    结构体:

复制代码
 1 // Cron keeps track of any number of entries, invoking the associated func as 2 // specified by the schedule. It may be started, stopped, and the entries may 3 // be inspected while running.  4 // Cron保持任意数量的条目的轨道,调用相关的func时间表指定。它可以被启动,停止和条目,可运行的同时进行检查。 5 type Cron struct { 6     entries  []*Entry        // 任务 7     stop     chan struct{}      // 叫停止的途径 8     add      chan *Entry        // 添加新任务的方式 9     snapshot chan []*Entry      // 请求获取任务快照的方式10     running  bool               // 是否在运行11     ErrorLog *log.Logger        // 出错日志(新增属性)12     location *time.Location     // 所在地区(新增属性)       13 }
复制代码

 

复制代码
 1 // Entry consists of a schedule and the func to execute on that schedule. 2 // 入口包括时间表和可在时间表上执行的func 3 type Entry struct { 4         // 计时器 5     Schedule Schedule 6     // 下次执行时间 7     Next time.Time 8     // 上次执行时间 9     Prev time.Time10     // 任务11     Job Job12 }
复制代码

 

    关键方法:

复制代码
 1 //  开始任务 2 // Start the cron scheduler in its own go-routine, or no-op if already started. 3 func (c *Cron) Start() { 4     if c.running { 5         return 6     } 7     c.running = true 8     go c.run() 9 }10 // 结束任务11 // Stop stops the cron scheduler if it is running; otherwise it does nothing.12 func (c *Cron) Stop() {13     if !c.running {14         return15     }16     c.stop <- struct{}{}17     c.running = false18 }19 20 // 执行定时任务21 // Run the scheduler.. this is private just due to the need to synchronize22 // access to the 'running' state variable.23 func (c *Cron) run() {24     // Figure out the next activation times for each entry.25     now := time.Now().In(c.location)26     for _, entry := range c.entries {27         entry.Next = entry.Schedule.Next(now)28     }29         // 无限循环30     for {31             //通过对下一个执行时间进行排序,判断那些任务是下一次被执行的,防在队列的前面.sort是用来做排序的32         sort.Sort(byTime(c.entries))33 34         var effective time.Time35         if len(c.entries) == 0 || c.entries[0].Next.IsZero() {36             // If there are no entries yet, just sleep - it still handles new entries37             // and stop requests.38             effective = now.AddDate(10, 0, 0)39         } else {40             effective = c.entries[0].Next41         }42 43         timer := time.NewTimer(effective.Sub(now))44         select {45         case now = <-timer.C:  // 执行当前任务46             now = now.In(c.location)47             // Run every entry whose next time was this effective time.48             for _, e := range c.entries {49                 if e.Next != effective {50                     break51                 }52                 go c.runWithRecovery(e.Job)53                 e.Prev = e.Next54                 e.Next = e.Schedule.Next(now)55             }56             continue57 58         case newEntry := <-c.add:  // 添加新的任务59             c.entries = append(c.entries, newEntry)60             newEntry.Next = newEntry.Schedule.Next(time.Now().In(c.location))61 62         case <-c.snapshot:  // 获取快照63             c.snapshot <- c.entrySnapshot()64 65         case <-c.stop:   // 停止任务66             timer.Stop()67             return68         }69 70         // 'now' should be updated after newEntry and snapshot cases.71         now = time.Now().In(c.location)72         timer.Stop()73     }74 }
复制代码

 

      spec.go

  结构体及关键方法:

复制代码
 1 // SpecSchedule specifies a duty cycle (to the second granularity), based on a 2 // traditional crontab specification. It is computed initially and stored as bit sets. 3 type SpecSchedule struct { 4     // 表达式中锁表明的,秒,分,时,日,月,周,每个都是uint64 5     // Dom:Day of Month,Dow:Day of week 6     Second, Minute, Hour, Dom, Month, Dow uint64 7 } 8  9 // bounds provides a range of acceptable values (plus a map of name to value).10 // 定义了表达式的结构体11 type bounds struct {12     min, max uint13     names    map[string]uint14 }15 16 17 // The bounds for each field.18 // 这样就能看出各个表达式的范围19 var (20        seconds = bounds{0, 59, nil}21        minutes = bounds{0, 59, nil}22        hours   = bounds{0, 23, nil}23        dom     = bounds{1, 31, nil}24        months  = bounds{1, 12, map[string]uint{25               "jan": 1,26               "feb": 2,27               "mar": 3,28               "apr": 4,29               "may": 5,30               "jun": 6,31               "jul": 7,32               "aug": 8,33               "sep": 9,34               "oct": 10,35               "nov": 11,36               "dec": 12,37        }}38        dow = bounds{0, 6, map[string]uint{39               "sun": 0,40               "mon": 1,41               "tue": 2,42               "wed": 3,43               "thu": 4,44               "fri": 5,45               "sat": 6,46        }}47 )48 49 const (50        // Set the top bit if a star was included in the expression.51        starBit = 1 << 6352 )
复制代码

 

  看了上面的东西肯定有人疑惑为什么秒分时这些都是定义了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的类。

 

  

复制代码
  1 package cron  2   3 import (  4     "fmt"  5     "math"  6     "strconv"  7     "strings"  8     "time"  9 ) 10  11 // Configuration options for creating a parser. Most options specify which 12 // fields should be included, while others enable features. If a field is not 13 // included the parser will assume a default value. These options do not change 14 // the order fields are parse in. 15 type ParseOption int 16  17 const ( 18     Second      ParseOption = 1 << iota // Seconds field, default 0 19     Minute                              // Minutes field, default 0 20     Hour                                // Hours field, default 0 21     Dom                                 // Day of month field, default * 22     Month                               // Month field, default * 23     Dow                                 // Day of week field, default * 24     DowOptional                         // Optional day of week field, default * 25     Descriptor                          // Allow descriptors such as @monthly, @weekly, etc. 26 ) 27  28 var places = []ParseOption{ 29     Second, 30     Minute, 31     Hour, 32     Dom, 33     Month, 34     Dow, 35 } 36  37 var defaults = []string{ 38     "0", 39     "0", 40     "0", 41     "*", 42     "*", 43     "*", 44 } 45  46 // A custom Parser that can be configured. 47 type Parser struct { 48     options   ParseOption 49     optionals int 50 } 51  52 // Creates a custom Parser with custom options. 53 // 54 //  // Standard parser without descriptors 55 //  specParser := NewParser(Minute | Hour | Dom | Month | Dow) 56 //  sched, err := specParser.Parse("0 0 15 */3 *") 57 // 58 //  // Same as above, just excludes time fields 59 //  subsParser := NewParser(Dom | Month | Dow) 60 //  sched, err := specParser.Parse("15 */3 *") 61 // 62 //  // Same as above, just makes Dow optional 63 //  subsParser := NewParser(Dom | Month | DowOptional) 64 //  sched, err := specParser.Parse("15 */3") 65 // 66 func NewParser(options ParseOption) Parser { 67     optionals := 0 68     if options&DowOptional > 0 { 69         options |= Dow 70         optionals++ 71     } 72     return Parser{options, optionals} 73 } 74  75 // Parse returns a new crontab schedule representing the given spec. 76 // It returns a descriptive error if the spec is not valid. 77 // It accepts crontab specs and features configured by NewParser. 78 // 将字符串解析成为SpecSchedule 。 SpecSchedule符合Schedule接口 79  80 func (p Parser) Parse(spec string) (Schedule, error) { 81   // 直接处理特殊的特殊的字符串 82     if spec[0] == '@' && p.options&Descriptor > 0 { 83         return parseDescriptor(spec) 84     } 85  86     // Figure out how many fields we need 87     max := 0 88     for _, place := range places { 89         if p.options&place > 0 { 90             max++ 91         } 92     } 93     min := max - p.optionals 94  95     // cron利用空白拆解出独立的items。 96     fields := strings.Fields(spec) 97  98     // 验证表达式取值范围 99     if count := len(fields); count < min || count > max {100         if min == max {101             return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)102         }103         return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)104     }105 106     // Fill in missing fields107     fields = expandFields(fields, p.options)108 109     var err error110     field := func(field string, r bounds) uint64 {111         if err != nil {112             return 0113         }114         var bits uint64115         bits, err = getField(field, r)116         return bits117     }118 119     var (120         second     = field(fields[0], seconds)121         minute     = field(fields[1], minutes)122         hour       = field(fields[2], hours)123         dayofmonth = field(fields[3], dom)124         month      = field(fields[4], months)125         dayofweek  = field(fields[5], dow)126     )127     if err != nil {128         return nil, err129     }130     // 返回所需要的SpecSchedule131     return &SpecSchedule{132         Second: second,133         Minute: minute,134         Hour:   hour,135         Dom:    dayofmonth,136         Month:  month,137         Dow:    dayofweek,138     }, nil139 }140 141 func expandFields(fields []string, options ParseOption) []string {142     n := 0143     count := len(fields)144     expFields := make([]string, len(places))145     copy(expFields, defaults)146     for i, place := range places {147         if options&place > 0 {148             expFields[i] = fields[n]149             n++150         }151         if n == count {152             break153         }154     }155     return expFields156 }157 158 var standardParser = NewParser(159     Minute | Hour | Dom | Month | Dow | Descriptor,160 )161 162 // ParseStandard returns a new crontab schedule representing the given standardSpec163 // (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always164 // pass 5 entries representing: minute, hour, day of month, month and day of week,165 // in that order. It returns a descriptive error if the spec is not valid.166 //167 // It accepts168 //   - Standard crontab specs, e.g. "* * * * ?"169 //   - Descriptors, e.g. "@midnight", "@every 1h30m"170 // 这里表示不仅可以使用cron表达式,也可以使用@midnight @every等方法171 172 func ParseStandard(standardSpec string) (Schedule, error) {173     return standardParser.Parse(standardSpec)174 }175 176 var defaultParser = NewParser(177     Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,178 )179 180 // Parse returns a new crontab schedule representing the given spec.181 // It returns a descriptive error if the spec is not valid.182 //183 // It accepts184 //   - Full crontab specs, e.g. "* * * * * ?"185 //   - Descriptors, e.g. "@midnight", "@every 1h30m"186 func Parse(spec string) (Schedule, error) {187     return defaultParser.Parse(spec)188 }189 190 // getField returns an Int with the bits set representing all of the times that191 // the field represents or error parsing field value.  A "field" is a comma-separated192 // list of "ranges".193 func getField(field string, r bounds) (uint64, error) {194     var bits uint64195     ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })196     for _, expr := range ranges {197         bit, err := getRange(expr, r)198         if err != nil {199             return bits, err200         }201         bits |= bit202     }203     return bits, nil204 }205 206 // getRange returns the bits indicated by the given expression:207 //   number | number "-" number [ "/" number ]208 // or error parsing range.209 func getRange(expr string, r bounds) (uint64, error) {210     var (211         start, end, step uint212         rangeAndStep     = strings.Split(expr, "/")213         lowAndHigh       = strings.Split(rangeAndStep[0], "-")214         singleDigit      = len(lowAndHigh) == 1215         err              error216     )217 218     var extra uint64219     if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {220         start = r.min221         end = r.max222         extra = starBit223     } else {224         start, err = parseIntOrName(lowAndHigh[0], r.names)225         if err != nil {226             return 0, err227         }228         switch len(lowAndHigh) {229         case 1:230             end = start231         case 2:232             end, err = parseIntOrName(lowAndHigh[1], r.names)233             if err != nil {234                 return 0, err235             }236         default:237             return 0, fmt.Errorf("Too many hyphens: %s", expr)238         }239     }240 241     switch len(rangeAndStep) {242     case 1:243         step = 1244     case 2:245         step, err = mustParseInt(rangeAndStep[1])246         if err != nil {247             return 0, err248         }249 250         // Special handling: "N/step" means "N-max/step".251         if singleDigit {252             end = r.max253         }254     default:255         return 0, fmt.Errorf("Too many slashes: %s", expr)256     }257 258     if start < r.min {259         return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)260     }261     if end > r.max {262         return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)263     }264     if start > end {265         return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)266     }267     if step == 0 {268         return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)269     }270 271     return getBits(start, end, step) | extra, nil272 }273 274 // parseIntOrName returns the (possibly-named) integer contained in expr.275 func parseIntOrName(expr string, names map[string]uint) (uint, error) {276     if names != nil {277         if namedInt, ok := names[strings.ToLower(expr)]; ok {278             return namedInt, nil279         }280     }281     return mustParseInt(expr)282 }283 284 // mustParseInt parses the given expression as an int or returns an error.285 func mustParseInt(expr string) (uint, error) {286     num, err := strconv.Atoi(expr)287     if err != nil {288         return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)289     }290     if num < 0 {291         return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)292     }293 294     return uint(num), nil295 }296 297 // getBits sets all bits in the range [min, max], modulo the given step size.298 func getBits(min, max, step uint) uint64 {299     var bits uint64300 301     // If step is 1, use shifts.302     if step == 1 {303         return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)304     }305 306     // Else, use a simple loop.307     for i := min; i <= max; i += step {308         bits |= 1 << i309     }310     return bits311 }312 313 // all returns all bits within the given bounds.  (plus the star bit)314 func all(r bounds) uint64 {315     return getBits(r.min, r.max, 1) | starBit316 }317 318 // parseDescriptor returns a predefined schedule for the expression, or error if none matches.319 func parseDescriptor(descriptor string) (Schedule, error) {320     switch descriptor {321     case "@yearly", "@annually":322         return &SpecSchedule{323             Second: 1 << seconds.min,324             Minute: 1 << minutes.min,325             Hour:   1 << hours.min,326             Dom:    1 << dom.min,327             Month:  1 << months.min,328             Dow:    all(dow),329         }, nil330 331     case "@monthly":332         return &SpecSchedule{333             Second: 1 << seconds.min,334             Minute: 1 << minutes.min,335             Hour:   1 << hours.min,336             Dom:    1 << dom.min,337             Month:  all(months),338             Dow:    all(dow),339         }, nil340 341     case "@weekly":342         return &SpecSchedule{343             Second: 1 << seconds.min,344             Minute: 1 << minutes.min,345             Hour:   1 << hours.min,346             Dom:    all(dom),347             Month:  all(months),348             Dow:    1 << dow.min,349         }, nil350 351     case "@daily", "@midnight":352         return &SpecSchedule{353             Second: 1 << seconds.min,354             Minute: 1 << minutes.min,355             Hour:   1 << hours.min,356             Dom:    all(dom),357             Month:  all(months),358             Dow:    all(dow),359         }, nil360 361     case "@hourly":362         return &SpecSchedule{363             Second: 1 << seconds.min,364             Minute: 1 << minutes.min,365             Hour:   all(hours),366             Dom:    all(dom),367             Month:  all(months),368             Dow:    all(dow),369         }, nil370     }371 372     const every = "@every "373     if strings.HasPrefix(descriptor, every) {374         duration, err := time.ParseDuration(descriptor[len(every):])375         if err != nil {376             return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)377         }378         return Every(duration), nil379     }380 381     return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)382 }
复制代码

 

项目中应用

   

复制代码
package mainimport (    "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

原创粉丝点击