Golang robfig/cron 實現解析

2021-02-19 Go語言中文網

robfig/cron是GO語言中一個定時執行註冊任務的package,  最近我在工程中使用到了它,由於它的實現優雅且簡單(主要是簡單),所以將源碼過了一遍,記錄和分享在此。

    

文檔:http://godoc.org/github.com/robfig/cron,repo: https://github.com/robfig/cron

基本玩法

Demo代碼如下,先用cron.New()初始化一個實例,然後調用AddFunc(spec string, cmd func()) 註冊你希望調用的func,第一個參數為調度的時間策略,第二個參數為到時間後執行的方法。robfig/cron支持非常多樣的時間策略(下面的代碼舉了一些例子),最後通過cron.Start()方法啟動。    

func TestCronDemo(t *testing.T) {  c := cron.New()    c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") })  c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") })  c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") })  c.AddFunc("@every 5m", func() { fmt.Println("every 5m, start 5m fron now") })          c.Start()    c.Stop()}
type cronJobDemo int
func (c cronJobDemo) Run() { fmt.Println("5s func trigger") return}

上面代碼中,第9、10行的代碼調用方法AddJob(spec string, cmd Job)也可以實現AddFunc註冊的功能,Job是interface,需要入參類型實現方法:Run()。實際上,方法AddFunc內部將參數cmd 進行了包裝(wrapper),然後也是調用方法AddJob進行註冊。

如果實際工程中定時執行的邏輯較為複雜,推薦使用方法AddJob()來註冊,自己寫方法Run(),這樣可以通過Run所屬的類型來傳遞所需數據,後面介紹都會說成AddJob,等效於AddFunc。

每當你用AddJob註冊一個定時調用策略,就會為這個策略生成一個唯一的Entry,不難想像,Entry裡會存儲被執行的時間、需要被調度執行的實體Job。

        生成entry後,再將entry放到struct Cron的entry列表裡,Cron的結構裡,主要是一些用來和外部交互的channel,比如通過channel添加、刪除entry等。詳見下面的代碼。

type Entry struct {    ID EntryID    Schedule Schedule        Next time.Time    Prev time.Time    WrappedJob Job    Job Job}type Cron struct {  entries   []*Entry                  chain     Chain              stop      chan struct{}       add       chan *Entry         remove    chan EntryID        snapshot  chan chan []Entry   running   bool                logger    Logger              runningMu sync.Mutex          location  *time.Location      parser    ScheduleParser      nextID    EntryID             jobWaiter sync.WaitGroup    }

    需要注意的是,WrappedJob和chain這兩個成員,這是Cron實現的Job封裝邏輯,目前是解決實際調度Job的異常處理。比如你希望自己的上一個時間點的JobA沒有結束,下一個時間點的JobA就不執行,這個「不執行」的邏輯實現就定義在chain,初始化時通過chain將JobA進行封裝寫入WrappedJob,那麼每次JobA調用前會先執行封裝邏輯,進行判斷。

        cron.Start()執行後,cron的後臺程序(方法run())就開始運行了。而它的主體,就是一個定時器的實現和到時後的job運行,加上cron裡的數據維護。

        cron的定時器實現是一個簡潔而典型的業務層實現,著重了解下,具體

的流程圖可見下圖。

        它的關鍵和值得學習之處是:

每個entry都包含自己下一次執行的絕對時間

先對entries按下次執行時間升序排序,只需要對第一個entry啟動定時器

定時器到時,只輪詢entries裡需要執行的entries,不需要全部輪詢。

且 執行的是當前時間之前的所有job,容錯高;

第一個定時器處理結束開啟下次定時器時,也只需要更新執行過的entries的下次執行時間,不需要更新所有的entries 

    上面的邏輯說完,程序主體已經清晰,除此之外,程序主體裡的定時器監聽和其他多個channel共用了select-case,這些channel在struct Cron裡能看到,實現了entries的動態添加、刪除、entries快照獲取等功能。代碼結構如下:

    將這些操作通過channel讓程序主體來操作,可以有效的減少互斥鎖的使用,也會引入問題,會導致有的job執行時間不是非常精準,導致某些entry被遺漏,:

比如最近的jobA的timer在1ms後就要到時,此時加入一個entry,耗時3ms

添加完entry後,再重新啟動timer(還是jobA的timer,此處還利 用了golang的time.NewTimer(d Duration)的入參為負數會立即到時的特點)

下次到時的時間必然不是jobA期待的執行時間(理論上晚了2ms)

    

channel的操作首先是非常簡潔省時的,其次,定時器實現裡,會掃描所有當前時間之前的entries來執行,增加了容錯性

值得稱讚的細節

interface的使用

struct Entry裡的Schedule和Cron裡的ScheduleParser都是interface,意味著我們是可以自己定製註冊job時的時間策略的格式的,只要自己實現時間策略的解析和獲取方法就好

這讓我想起了以前看過golang裡什麼時候用interface和struct的討論,我覺得這是個很好的例子:預期對同一個接口有多個實現時就抽象成interface,不知道該不該用就用struct。

wrapper的實現

上面有提到,通過對Job的封裝,cron實現了同一個job多次調用時的異常處理等,值得以後在實踐中借鑑。

最後是我加了一點注釋的代碼

https://github.com/jiangz222/cron/tree/comments-v3

推薦閱讀

喜歡本文的朋友,歡迎關注「Go語言中文網」:

Go語言中文網啟用微信學習交流群,歡迎加微信:274768166,投稿亦歡迎

相關焦點

  • Go 每日一庫之 cron
    goroutine 18 [running]:github.com/robfig/cron/v3.Recover.func1.1.1(0x514ee0, 0xc0000044a0) D:/code/golang/pkg/mod/github.com/robfig/cron/v3@v3.0.1/chain.go:45 +0xbcpanic(0x4cf380
  • .net core 實現基於 cron 表達式的任務調度
    cron 表達式的任務調度Intro上次我們實現了一個簡單的基於 Timer 的定時任務,詳細信息可以看這篇文章 。但是使用過程中慢慢發現這種方式可能並不太合適,有些任務可能只希望在某個時間段內執行,只使用 timer 就顯得不是那麼靈活了,希望可以像 quartz 那樣指定一個 cron 表達式來指定任務的執行時間。cron 表達式介紹cron 常見於Unix和類Unix的作業系統之中,用於設置周期性被執行的指令。
  • 解讀 pkg.go.dev 的設計和實現:設計篇
    當我深入研究 pkg.go.dev 源碼後,我失望了,無論是設計還是實現,水平都很一般。本想著放棄這一系列,但想想還是繼續,一方面嘗試指出問題,給出認為正確的做法;另一方面,畢竟是官方的項目,開源了相信它會變得更好。
  • golang mod 入門
    go modules 是 golang 1.11 新加的特性。現在1.12 已經發布了,是時候用起來了。Modules官方定義為:模塊是相關Go包的集合。modules是原始碼交換和版本控制的單元。 go命令直接支持使用modules,包括記錄和解析對其他模塊的依賴性。modules替換舊的基於GOPATH的方法來指定在給定構建中使用哪些源文件。
  • 使用golang解析xmpp流並進行處理
    因業務需求,需要在不對ejabberd進行修改的情況下,對用戶發送的消息進行過濾和統計,實現禁言用戶等。
  • golang 實現 mysql 資料庫備份
    發現golang沒有這個問題,正好用go重寫,來熟悉golang。一些關鍵點map & json,在處理主鍵與外鍵信息時,需要用到json數據結構來存儲中間結果,因為要靈活處理,在golang中只能用map[string]interface{}來處理。
  • .NET平臺開源項目之Cron任務調度CronNET
    一、Cron介紹和工具cron是一個linux下的定時執行工具,可以在無需人工幹預的情況下運行作業。cron伺服器可以根據配置文件約定的時間來執行特定的作務。所以其他組件和平臺根據其語法做了支持,就有了各種各樣的語言版本。
  • seata-golang 接入指南
    seata-golang 是一個分布式事務框架,實現了 AT 模式和 TCC
  • Golang Post Multipart/form-data
    當需要在 golang 上發送這個請求的時候,就遇到了各種問題網上搜到了一些方法,諸如,multipart.createFormFile 等等,關鍵是還是不對。= w.FormDataContentType()req.Header.Set("Content-Type", contentType)但是,你會發發現,即使你設置了你的 Content-Type 是 multipart/form-data,debug 也能看到你的 body 裡面有這個文件的所有數據,但是對方伺服器還是不一定能解析出來,完全看服務端的同學怎麼實現、怎麼解析。
  • golang語言學習總結
    ", ps.ByName("first_name"), ps.ByName("last_name"))}golang 當import依賴同名的時候處理方式(hp 類似別名)hp "github.com/kirinlabs/HttpRequest"golang interface類型轉string
  • Golang 性能優化實戰
    golang 部分細節簡介在優化之前,我們需要對 golang 的實現細節有一個簡單的了解,才能明白哪些地方有問題,哪些地方可以優化,以及怎麼優化。以下內容的詳細講解建議查閱網上優秀的 blog。對語言的底層實現機制最好有個基本的了解,否則有時候掉到坑裡都不知道為啥。
  • 玩轉 Linux cron
    看下代碼實現:for (u = db->head;  u != NULL;  u = u->next) {                for (e = u->crontab;  e !
  • golang每日一題(golang map)
    所以這裡順便用最簡單的語言總結一下java的hashmap和golang的map對比(這兩個都是語言設計精髓,展開講可以出書)Java的mapJava的map有哪幾種實現?jdk為數據結構中的映射定義了一個接口java.util.Map,裡面有四種實現類:hashmap,hashtable,LinkedHashMap,TreeMap
  • Cron 任務入門指南 | Linux 中國
    Cron 任務入門指南cron 任務的典型格式是:分鐘(0-59) 小時(0-24) 日(1-31) 月(1-12) 星期(0-6) 要執行的命令只需記住 cron 任務的格式或列印下面的插圖並將其放在你桌面上即可。在上圖中,星號表示特定的時間塊。
  • 走進 Golang 之編譯器原理
    為了理解 Golang 從原始碼翻譯到 Token 的過程,我們用一段代碼來看一下翻譯的一一對應情況。圖中重要的地方我都進行了注釋,不過這裡還是有幾句話多說一下,我們看著上面的代碼想像一下,如果要我們自己來實現這個「翻譯工作」,程序要如何識別 Token 呢?首先先來給 Go 的 token 類型分個類:變量名、字面量、操作符、分隔符以及關鍵字。
  • 詳解Unix和Linux作業系統中Cron的用法
    服務提供crontab命令來設定cron服務的,以下是這個命令的一些參數與說明:  crontab -u //設定某個用戶的cron服務,一般root用戶在執行這個命令的時候需要此參數  crontab -l //列出某個用戶cron服務的詳細內容  crontab -r //刪除沒個用戶的cron服務  crontab -e //編輯某個用戶的
  • 小白也能學會的golang map的實現(二)
    本文在golang map 數據結構的基礎上,學習如何通過make 構造一個map數據。
  • Golang 深度剖析 -- 理解Mutex源碼實現
    在golang的並發編程中,為了保證訪問數據的安全性,經常會用到mutex去保證goroutine的同步,本文基於golang 1.14.6版本去分析
  • K8S Jobs/Cron Jobs入門教程,輕鬆搞定批處理
    你可以用cron表達式創建一個Kubernetes cronjob。Job會按照你在job中提到的時間表自動啟動。下面我們將介紹如何指定一個cron計劃,你可以使用crontab生成器(https://crontab-generator.org/)來生成自己的時間計劃。
  • 萬字長文解讀 pkg.go.dev 的設計和實現
    為了應對這種情況,所以出現了架構圖中的 Frontend Task Queue,用於獲取資料庫中還不存在的包並將包 master 分支的信息顯示給用戶,不過截止目前還未實現。不知道到時候以及會如何實現。這裡不得不吐槽了:這樣的問題在設計之初就應該考慮。