Gotorch - 多機定時任務管理系統

前言

最近在學習 Go 語言,遵循着 「學一門語言最好的方式是使用它」 的理念,想着用 Go 來實現些什麼,恰好工做中一直有一個比較讓我煩惱的問題,因而用 Go 解決一下,即便不在生產環境使用,也能夠做爲 Go 語言學習的一種方式。css

先介紹下問題:html

組內有十來臺機器,上面用 cron 分別定時執行着一些腳本和 shell 命令,一開始任務少的時候,你們都記得哪臺機器執行着什麼,隨着時間推移,人員幾經變更,任務也愈來愈多,再也沒人能記得清哪些任務在哪些機器上執行了,排查和解決後臺腳本的問題也愈來愈麻煩。linux

解決這個問題也不是沒有辦法:nginx

  • 維護一個 wiki,一旦任務有變更就更新 wiki,但一旦忘記更新 wiki,任務就會變成孤兒,何時出了問題更很差查。
  • 佈置一臺機器,定時拉取各機器的 cron 配置文件,進行對比統計,再將結果彙總展現,但命令的寫法各式各樣,對比命令也是個沒頭腦的事。
  • 使用開源分佈式任務調度任務,比較重型,並且通常要佈置數據庫、後臺,比較麻煩。

除此以外,任務的修改也很是不方便,若是想給在 crontab 裏修改某一項任務,還須要找運維操做。雖然解決這個問題也有辦法,使用 crontab cronfile.txt 直接讓 crontab 加載文件,但引入新的問題:任務文件加載的實時性很差控制。git

爲了解決以上問題,我結合 cron 和任務管理,天天下班後花一點時間,實現一個小功能,最後完成了 gotorch 的可用版。看着 GitHub 的 commit 統計,還挺有成就感的~github

 

這裏放上 GitHub 連接地址: GitHub-zhenbianshu-gotorch ,歡迎 star/fork/issueweb

介紹一下特點功能:shell

  • cron+,秒級定時,使任務執行更加靈活;
  • 任務列表文件路徑能夠自定義,建議使用版本控制系統;
  • 內置日誌和監控系統,方便各位同窗任意擴展;
  • 平滑重加載配置文件,一旦配置文件有變更,在不影響正在執行的任務的前提下,平滑加載;
  • IP、最大執行數、任務類型配置,支持更靈活的任務配置;

下面說一下功能實現的技術要點:數據庫

文章歡迎轉載,但請帶上本文源地址:http://www.cnblogs.com/zhenbianshu/p/7905678.html,謝謝。編程


cron+

在實現相似 cron 的功能以前,我簡單地看了一下 cron 的源碼,源碼在 https://busybox.net/downloads/ 能夠下載,解壓後文件在miscutils > crond.c

cron 的實現設計得很巧妙的,大概以下:

數據結構:

  1. cron 擁有一個全局結構體 global ,保存着各個用戶的任務列表;
  2. 每個任務列表是一個結構體 CronFile, 保存着用戶名和任務鏈表等;
  3. 每個任務 CronLine 有 shell 命令、執行 pid、執行時間數組 cl_Time 等屬性;
  4. 執行時間數組的最大長度根據 「分時日月周」 的最大值肯定,將可執行時間點的值置爲 true,例如 在天天的 3 點執行則 cl_Hrs[3]=true

執行方式:

  1. cron是一個 while(true) 式的長循環,每次 sleep 到下一分鐘的開始。
  2. cron 在每分鐘的開始會依次遍歷檢查用戶 cron 配置文件,將更新後的配置文件解析成任務存入全局結構體,同時它也按期檢查配置文件是否被修改。
  3. 而後 cron 會將當前時間解析爲 第 n 分/時/日/月/周,並判斷 cal_Time[n] 全爲 true 則執行任務。
  4. 執行任務時將 pid 寫入防止重複執行;
  5. 後續 cron 還會進行一些異常檢測和錯誤處理操做。

明白了 cron 的執行方式後,感受每一個時間單位都遍歷任務進行判斷於性能有損耗,並且我實現的是秒級執行,遍歷判斷的性能損耗更大,因而考慮優化成:

給每一個任務設置一個 next_time 的時間戳,在一次執行後更新此時間戳,每一個時間單位只須要判斷 task.next_time == current_time

後來因爲 「秒分時日月周」 的日期格式進位不規則,代碼太複雜,實現出來效率也不比原來好,終於放棄了這種想法。。採用了跟 cron 同樣的執行思路。

此外,我添加了三種限制任務執行的方式:

  • IP:在服務啓動時獲取本地內網 IP,執行前校驗是否在任務的 IP 列表中;
  • 任務類型:任務爲 daemon 的,當任務沒有正在執行時則中斷判斷直接啓動;
  • 最大執行數:在每一個任務上設置一個執行中任務的 pid 構成的 slice,每次執行前校驗當前執行數。

而任務啓動方式,則直接使用 goroutine 配合 exec 包,每次執行任務都啓動一個新的 goroutine,保存 pid,同時進行錯誤處理。因爲服務可能會在一秒內屢次掃描任務,我給每一個任務添加了一個進程上次執行時間戳的屬性,待下次執行時對比,防止任務在一秒內屢次掃描執行了屢次。


守護進程

本服務是作成了一個相似 nginx 的服務,我將進程的 pid 保存在一個臨時文件中,對進程操做時經過命令行給進程發送信號,只須要注意下異常狀況下及時清理 pid 文件就行了。

這裏說一下 Go 守護進程的建立方式:

因爲 Go 程序在啓動時 runtime 可能會建立多個線程(用於內存管理,垃圾回收,goroutine管理等),而 fork 與多線程環境並不能和諧共存,因此 Go 中沒有 Unix 系統中的 fork 方法;因而啓動守護進程我採用 exec 以後當即執行,即 fork and exec 的方式,而 Go 的 exec 包則支持這種方式。

在進程最開始時獲取並判斷進程 ppid 是否爲1 (守護進程的父進程退出,進程會被「過繼」給 init 進程,其進程號爲1),在父進程的進程號不爲1時,使用原進程的全部參數 fork and exec 一個跟本身相同的進程,關閉新進程與終端的聯繫,並退出原進程。

filePath, _ := filepath.Abs(os.Args[0]) // 獲取服務的命令路徑
    cmd := exec.Command(filePath, os.Args[1:]...) // 使用自身的命令路徑、參數建立一個新的命令
    cmd.Stdin = nil
    cmd.Stdout = nil 
    cmd.Stderr = nil // 關閉進程標準輸入、標準輸出、錯誤輸出
    cmd.Start() // 新進程執行
    return // 父進程退出

信號處理

將進程製做爲守護進程以後,進程與外界的通訊就只好依靠信號了,Go 的 signal 包搭配 goroutine 能夠方便地監聽、處理信號。同時咱們使用 syscall 包內的 Kill 方法來向進程發送信號。

咱們監聽 Kill 默認發送的信號 SIGTERM,用來處理服務退出前的清理工做,另外我還使用了用戶自定義信號 SIGUSR2 用來做爲終端通知服務重啓的消息。

一個信號從監聽到捕捉再處處理的完整流程以下:

  1. 首先咱們使用建立一個類型爲 os.Sygnal 的無緩衝channel,來存放信號。
  2. 使用 signal.Notify() 函數註冊要監聽的信號,傳入剛建立的 channel,在捕捉到信號時接收信號。
  3. 建立一個 goroutine,在 channel 中沒有信號時 signal := <-channel 會阻塞。
  4. Go 程序一旦捕捉到正在監聽的信號,就會把信號經過 channel 傳遞過來,此時 goroutine 便不會繼續阻塞。
  5. 經過後面的代碼處理對應的信號。

對應的代碼以下:

c := make(chan os.Signal)
    signal.Notify(c, syscall.SIGTERM, syscall.SIGUSR2) 

    // 開啓一個goroutine異步處理信號
    go func() {
        s := <-c
        if s == syscall.SIGTERM {
            task.End()
            logger.Debug("bootstrap", "action: end", "pid "+strconv.Itoa(os.Getpid()), "signal "+fmt.Sprintf("%d", s))
            os.Exit(0)
        } else if s == syscall.SIGUSR2 {
            task.End()
            bootStrap(true)
        }
    }()

小結

gotorch 的開發共花了三個月,天天半小時左右,1~3 個 commits,經歷了三次大的重構,特別是在代碼格式上改得比較頻繁。 不過使用 Go 開發確實是挺舒心的,Go 的代碼很簡潔, gofmt 用着很是方便。另外 Go 的學習曲線也挺平滑,熟悉各個經常使用標準包後就能進行簡單的開發了。 簡單易學、高效快捷,難怪 Go 火熱得這麼快了。

關於本文有什麼問題能夠在下面留言交流,若是您以爲本文對您有幫助,能夠點擊下面的 推薦 支持一下我,博客一直在更新,歡迎 關注

參考:

論fork()函數與Linux中的多線程編程

linux 信號量之SIGNAL詳解

相關文章
相關標籤/搜索