利用 Etcd 的Lease租約特性來實現(xiàn)定時功能,同時通過Watch機制來實現(xiàn)多節(jié)點情況下只有一個節(jié)點執(zhí)行該任務(wù)。通過定時任務(wù)庫 Cron 的時間字符串解析器Parser來解析任務(wù)執(zhí)行時間。
創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),德惠企業(yè)網(wǎng)站建設(shè),德惠品牌網(wǎng)站建設(shè),網(wǎng)站定制,德惠網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,德惠網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
Etcd
Cron
源碼鏈接
在我們編碼過程中,經(jīng)常會用到與時間相關(guān)的需求。而關(guān)于時間轉(zhuǎn)換之類的比較簡單,那么計時器經(jīng)過了以下幾個版本的迭代:
Go1.10之前的計時器的結(jié)構(gòu)體如下所示
注意這個結(jié)構(gòu)體是用var變量定義的,它會存儲所有的計時器。t就是最小四叉堆,運行時創(chuàng)建的所有計時器都會加入到四叉堆中。
由一個獨立的timerproc通過最小四叉堆和futexsleep來管理定時任務(wù)。
但是全局四叉堆共用一把鎖對性能的影響非常大,所以Go1.10之后將全局四叉堆分割成了64個更小的四叉堆。
在理想情況下,四叉堆的數(shù)量應(yīng)該等于處理器的數(shù)量GOMAXPROCS,但是需要動態(tài)獲取處理的數(shù)量,所以經(jīng)過權(quán)衡初始化64個四叉堆,如果當(dāng)前機器的處理器P的個數(shù)超過了64個,多個處理器的計時器可能會存儲在同一個桶中。
將全局計時器分片,雖然能夠降低鎖的粒度,但是timerproc造成處理器和線程之間頻繁的上下文切換卻成為了影響計時器的瓶頸。
在Go1.14版本之后,計時器桶timersBucket已經(jīng)被移除了,所有的計時器都以最小四叉堆的形式存儲在P中。
在p結(jié)構(gòu)體中有以下字段與計時器關(guān)聯(lián):
而計時器的結(jié)構(gòu)體為:
這個僅僅只是runtime/time.go運行時內(nèi)部處理的結(jié)構(gòu),而真正對外暴露的計時器的結(jié)構(gòu)體是:
通過channel來通知計時器時間
在runtime/time.go文件下,我們可以看到下面幾個方法:
當(dāng)通過time.NewTimer方法增加新的計時器時,會執(zhí)行startTimer來增加計時器
狀態(tài)從timerNoStatus-timerWaiting,其他狀態(tài)會拋出異常
1、調(diào)用cleantimers清除處理器P中的計時器,可以加快創(chuàng)建和刪除計時器的程序速度
2、調(diào)用doaddtimer將當(dāng)前計時器加入到處理器P的四叉堆timers中
3、調(diào)用wakeNetPoller喚醒網(wǎng)絡(luò)輪詢器中休眠的線程,檢查timer被喚醒的時間when是否在當(dāng)前輪詢預(yù)期的運行時間內(nèi),如果是就喚醒。
當(dāng)通過調(diào)用timer.Stop停止計時器時,會執(zhí)行stopTimer來停止計時器
deltimer會標(biāo)記需要刪除的計時器。在刪除計時器的過程中,可能會遇到其他處理器P的計時器,所以我們僅僅只是將狀態(tài)標(biāo)記為刪除,處理器P執(zhí)行刪除操作。
當(dāng)通過調(diào)用timer.Reset重置定時器時,會執(zhí)行resetTimer來重置定時器
modtimer會修改已經(jīng)存在的計時器,會根據(jù)以下規(guī)則處理計時器狀態(tài)
狀態(tài)為timerNoStatus, timerRemoved會被標(biāo)記為已刪除wasRemoved,就會調(diào)用doaddtimer新創(chuàng)建一個計時器。
而在正常情況下會根據(jù)修改后的時間進行不同的處理:
會根據(jù)狀態(tài)清除處理器P的最小四叉堆隊頭的計時器
在GPM調(diào)度的時候檢查計時器
與cleantimers不同的是,adjusttimers會遍歷處理器P轉(zhuǎn)給你所有的計時器
會檢查四叉堆堆頂?shù)挠嫊r器,根據(jù)狀態(tài)處理計時器
1、狀態(tài)是timerDeleted,狀態(tài)變?yōu)閠imerDeleted,然后刪除計時器,再變更狀態(tài)為timerRemoved
2、狀態(tài)是timerModifiedXXX
3、狀態(tài)是timerWaiting,如果計時器沒有到達觸發(fā)時間,直接返回,否則狀態(tài)變?yōu)閠imerRunning,調(diào)用runOneTimer運行堆頂?shù)挠嫊r器
根據(jù)period字段是否大于0判斷,如果大于0
如果小于等于0:
更新完狀態(tài)后,回調(diào)函數(shù)f(arg, seq)執(zhí)行方法。
在adjesttimers中提到過
checkTimers是調(diào)度器用來運行處理器P中定時器的函數(shù),會在以下幾種情況被觸發(fā):
1、先通過處理器P字段中updateTimer0When判斷是否有需要執(zhí)行的計時器,如果沒有直接返回
2、如果下一個計時器沒有到期但是需要刪除的計時器較少時會直接返回
3、加鎖
4、需要處理的timer,根據(jù)時間將timers切片中的timer重新排序,調(diào)用adjusttimers
5、會通過runtimer依次查找運行計時器
6、處理器中已刪除的timer大于p上的timer數(shù)量的1/4,對標(biāo)記為timerDeleted的timer進行清理
7、解鎖
go1.10最多可以創(chuàng)建GOMAXPROCS數(shù)量的timerproc協(xié)程,當(dāng)然不超過64。但我們要知道timerproc自身就是協(xié)程,也需要runtime pmg的調(diào)度。到go 1.14把檢查到期定時任務(wù)的工作交給了網(wǎng)絡(luò)輪詢器,不需要額外的調(diào)度,每次runtime.schedule和findrunable時直接運行到期的定時任務(wù)。
在linux下實現(xiàn)定時器主要有如下方式
在這當(dāng)中 基于時間輪方式實現(xiàn)的定時器 時間復(fù)雜度最小,效率最高,然而我們可以通過 優(yōu)先隊列 實現(xiàn)時間輪定時器。
優(yōu)先隊列的實現(xiàn)可以使用最大堆和最小堆,因此在隊列中所有的數(shù)據(jù)都可以定義排序規(guī)則自動排序。我們直接通過隊列中 pop 函數(shù)獲取數(shù)據(jù),就是我們按照自定義排序規(guī)則想要的數(shù)據(jù)。
在 Golang 中實現(xiàn)一個優(yōu)先隊列異常簡單,在 container/head 包中已經(jīng)幫我們封裝了,實現(xiàn)的細節(jié),我們只需要實現(xiàn)特定的接口就可以。
下面是官方提供的例子
因為優(yōu)先隊列底層數(shù)據(jù)結(jié)構(gòu)是由二叉樹構(gòu)建的,所以我們可以通過數(shù)組來保存二叉樹上的每一個節(jié)點。
改數(shù)組需要實現(xiàn) Go 預(yù)先定義的接口 Len , Less , Swap , Push , Pop 和 update 。
timerType結(jié)構(gòu)是定時任務(wù)抽象結(jié)構(gòu)
首先的 start 函數(shù),當(dāng)創(chuàng)建一個 TimeingWheel 時,通過一個 goroutine 來執(zhí)行 start ,在start中for循環(huán)和select來監(jiān)控不同的channel的狀態(tài)
通過for循環(huán)從隊列中取數(shù)據(jù),直到該隊列為空或者是遇見第一個當(dāng)前時間比任務(wù)開始時間大的任務(wù), append 到 expired 中。因為優(yōu)先隊列中是根據(jù) expiration 來排序的,
所以當(dāng)取到第一個定時任務(wù)未到的任務(wù)時,表示該定時任務(wù)以后的任務(wù)都未到時間。
當(dāng) getExpired 函數(shù)取出隊列中要執(zhí)行的任務(wù)時,當(dāng)有的定時任務(wù)需要不斷執(zhí)行,所以就需要判斷是否該定時任務(wù)需要重新放回優(yōu)先隊列中。 isRepeat 是通過判斷任務(wù)中 interval 是否大于 0 判斷,
如果大于0 則,表示永久就生效。
防止外部濫用,阻塞定時器協(xié)程,框架又一次封裝了timer這個包,名為 timer_wapper 這個包,它提供了兩種調(diào)用方式。
參數(shù)和上面的參數(shù)一樣,只是在第三個參數(shù)中使用了任務(wù)池,將定時任務(wù)放入了任務(wù)池中。定時任務(wù)的本身執(zhí)行就是一個 put 操作。
至于put以后,那就是 workers 這個包管理的了。在 worker 包中, 也就是維護了一個任務(wù)池,任務(wù)池中的任務(wù)會有序的執(zhí)行,方便管理。