Gin實踐 連載十一 Cron定時任務

原文地址:Cron定時任務
項目地址:https://github.com/EDDYCJY/go...html

若是對你有所幫助,歡迎點個 Star 或贊 😄git

在實際的應用項目中,定時任務的使用是很常見的。你是否有過 Golang 如何作定時任務的疑問,莫非是輪詢?github

在本文中咱們將結合咱們的項目講述 Crongolang

介紹

咱們將使用 cron 這個包,它實現了 cron 規範解析器和任務運行器,簡單來說就是包含了定時任務所需的功能segmentfault

Cron 表達式格式

字段名 是否必填 容許的值 容許的特殊字符
秒(Seconds) Yes 0-59 * / , -
分(Minutes) Yes 0-59 * / , -
時(Hours) Yes 0-23 * / , -
一個月中的某天(Day of month) Yes 1-31 * / , - ?
月(Month) Yes 1-12 or JAN-DEC * / , -
星期幾(Day of week) Yes 0-6 or SUN-SAT * / , - ?

Cron表達式表示一組時間,使用 6 個空格分隔的字段緩存

能夠留意到 Golang 的 Cron 比 Crontab 多了一個秒級,之後遇到秒級要求的時候就省事了app

Cron 特殊字符

一、星號 ( * ) 優化

星號表示將匹配字段的全部值url

二、斜線 ( / )日誌

斜線用戶 描述範圍的增量,表現爲 「N-MAX/x」,first-last/x 的形式,例如 3-59/15 表示此時的第三分鐘和此後的每 15 分鐘,到59分鐘爲止。即從 N 開始,使用增量直到該特定範圍結束。它不會重複

三、逗號 ( , )

逗號用於分隔列表中的項目。例如,在 Day of week 使用「MON,WED,FRI」將意味着星期一,星期三和星期五

四、連字符 ( - )

連字符用於定義範圍。例如,9 - 17 表示從上午 9 點到下午 5 點的每一個小時

五、問號 ( ? )

不指定值,用於代替 「 * 」,相似 「 _ 」 的存在,不難理解

預約義的 Cron 時間表

輸入 簡述 至關於
@yearly (or @annually) 1月1日午夜運行一次 0 0 0 1 1 *
@monthly 每月的午夜,每月的第一個月運行一次 0 0 0 1
@weekly 每週一次,週日午夜運行一次 0 0 0 0
@daily (or @midnight) 天天午夜運行一次 0 0 0 *
@hourly 每小時運行一次 0 0

安裝

$ go get -u github.com/robfig/cron

實踐

在上一章節 Gin實踐 連載十 定製 GORM Callbacks 中,咱們使用了 GORM 的回調實現了軟刪除,同時也引入了另一個問題

就是我怎麼硬刪除,我何時硬刪除?這個每每與業務場景有關係,大體爲

  • 另外有一套硬刪除接口
  • 定時任務清理(或轉移、backup)無效數據

在這裏咱們選用第二種解決方案來進行實踐

編寫硬刪除代碼

打開 models 目錄下的 tag.go、article.go文件,分別添加如下代碼

一、tag.go

func CleanAllTag() bool {
    db.Unscoped().Where("deleted_on != ? ", 0).Delete(&Tag{})

    return true
}

二、article.go

func CleanAllArticle() bool {
    db.Unscoped().Where("deleted_on != ? ", 0).Delete(&Article{})

    return true
}

注意硬刪除要使用 Unscoped(),這是 GORM 的約定

編寫Cron

在 項目根目錄下新建 cron.go 文件,用於編寫定時任務的代碼,寫入文件內容

package main

import (
    "time"
    "log"

    "github.com/robfig/cron"

    "github.com/EDDYCJY/go-gin-example/models"
)

func main() {
    log.Println("Starting...")

    c := cron.New()
    c.AddFunc("* * * * * *", func() {
        log.Println("Run models.CleanAllTag...")
        models.CleanAllTag()
    })
    c.AddFunc("* * * * * *", func() {
        log.Println("Run models.CleanAllArticle...")
        models.CleanAllArticle()
    })

    c.Start()

    t1 := time.NewTimer(time.Second * 10)
    for {
        select {
        case <-t1.C:
            t1.Reset(time.Second * 10)
        }
    }
}

在這段程序中,咱們作了以下的事情

一、cron.New()

會根據本地時間建立一個新(空白)的 Cron job runner

func New() *Cron {
    return NewWithLocation(time.Now().Location())
}

// NewWithLocation returns a new Cron job runner.
func NewWithLocation(location *time.Location) *Cron {
    return &Cron{
        entries:  nil,
        add:      make(chan *Entry),
        stop:     make(chan struct{}),
        snapshot: make(chan []*Entry),
        running:  false,
        ErrorLog: nil,
        location: location,
    }
}

二、c.AddFunc()

AddFunc 會向 Cron job runner 添加一個 func ,以按給定的時間表運行

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

會首先解析時間表,若是填寫有問題會直接 err,無誤則將 func 添加到 Schedule 隊列中等待執行

func (c *Cron) Schedule(schedule Schedule, cmd Job) {
    entry := &Entry{
        Schedule: schedule,
        Job:      cmd,
    }
    if !c.running {
        c.entries = append(c.entries, entry)
        return
    }

    c.add <- entry
}

三、c.Start()

在當前執行的程序中啓動 Cron 調度程序。其實這裏的主體是 goroutine + for + select + timer 的調度控制哦

func (c *Cron) Run() {
    if c.running {
        return
    }
    c.running = true
    c.run()
}

四、time.NewTimer + for + select + t1.Reset

若是你是初學者,大概會有疑問,這是幹嗎用的?

(1)time.NewTimer

會建立一個新的定時器,持續你設定的時間 d 後發送一個 channel 消息

(2)for + select

阻塞 select 等待 channel

(3)t1.Reset

會重置定時器,讓它從新開始計時
(注意,本文適用於 「t.C已經取走,可直接使用 Reset」)


總的來講,這段程序是爲了阻塞主程序而編寫的,但願你帶着疑問來想,有沒有別的辦法呢?

有的,你直接 select{} 也能夠完成這個需求 :)

驗證

$ go run cron.go 
2018/04/29 17:03:34 [info] replacing callback `gorm:update_time_stamp` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:56
2018/04/29 17:03:34 [info] replacing callback `gorm:update_time_stamp` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:57
2018/04/29 17:03:34 [info] replacing callback `gorm:delete` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:58
2018/04/29 17:03:34 Starting...
2018/04/29 17:03:35 Run models.CleanAllArticle...
2018/04/29 17:03:35 Run models.CleanAllTag...
2018/04/29 17:03:36 Run models.CleanAllArticle...
2018/04/29 17:03:36 Run models.CleanAllTag...
2018/04/29 17:03:37 Run models.CleanAllTag...
2018/04/29 17:03:37 Run models.CleanAllArticle...

檢查輸出日誌正常,模擬已軟刪除的數據,定時任務工做OK

小結

定時任務很常見,但願你經過本文可以熟知 Golang 怎麼實現一個簡單的定時任務調度管理

能夠不依賴系統的 Crontab 設置,指不定哪一天就用上了呢

參考

本系列示例代碼

本系列目錄

相關文章
相關標籤/搜索