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

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前言

最近在學習 Go 語言,遵循著 “學一門語言最好的方式是使用它” 的理念,想著用 Go 來實現些什麼,剛好工作中一直有一個比較讓我煩惱的問題,於是用 Go 解決一下,即使不在生產環境使用,也可以作為 Go 語言學習的一種方式。

先介紹下問題:

組內有十來台機器,上面用 cron 分別定時執行著一些指令碼和 shell 命令,一開始任務少的時候,大家都記得哪台機器執行著什麼,隨著時間推移,人員幾經變動,任務也越來越多,再也沒人能記得清哪些任務在哪些機器上執行了,排查和解決後台指令碼的問題也越來越麻煩。

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

  • 維護一個 wiki,一旦任務有變動就更新 wiki,但一旦忘記更新 wiki,任務就會變成孤兒,什麼時候出了問題更不好查。
  • 布置一台機器,定時拉取各機器的 cron 設定檔,進行對比統計,再將結果匯總展示,但命令的寫法各式各樣,對比命令也是個沒頭腦的事。
  • 使用開源分布式任務調度任務,比較重型,而且一般要布置資料庫、後台,比較麻煩。

除此之外,任務的修改也非常不方便,如果想給在 crontab 裡修改某一項任務,還需要找營運操作。雖然解決這個問題也有辦法,使用 crontab cronfile.txt 直接讓 crontab 負載檔案,但引入新的問題:任務檔案載入的即時性不好控制。

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

 

這裡放上 GitHub 連結地址: GitHub-zhenbianshu-gotorch ,歡迎 star/fork/issue

介紹一下特色功能:

  • 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詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.