摘要:在軟件項目中,定時器也被應用到了各方各面,本文將從項目入手,講述定時器,本文的例子都以為例。定時器總類定時器有兩種對應重復任務和一次性任務。
在大規模分布式系統中,每個業務都可能是集群,每個業務機都會產生定時任務,不同的業務會有不同的任務管理需求,統一的任務調度和管理變得非常有必要。
定時如何準確,大量的定時被同時觸發怎么辦?
定時結束的時候,怎么通知業務機去處理呢?
某臺業務機下線了怎么辦?
如何提供任務更新、刪除功能?
基本模型如下圖:
定時器在社會中有著廣泛的應用,比如每天叫你起床的鬧鐘。在軟件項目中,定時器也被應用到了各方各面,本文將從 web 項目入手,講述定時器,本文的例子都以 node 為例。
為什么要用定時器?沒有什么比機器更加準時!在我接觸單片機的時候,已經開始感嘆,為什么機器時間可以做到這么準!
比如文章的定時發布、商品的準點開始搶購、活動定時上下架,肯定不會是一個又一個管理員在后臺幫你點擊按鈕,完成操作!系統的準時可以定位到毫秒級,雖然每個用戶可能和服務器的時間不一致,秒級的差別還是在可接受范圍的,但是在某些領域也會有很多精細到毫秒級的定時任務需求,比如航空航天、定時炸彈等等。
定時器總類定時器有兩種 interval、timeout, 對應重復任務和一次性任務。在我的理解里,interval 任務只是在 timeout 的時候再次注冊了本任務。
// 重復性任務 var timer = setInterval(function(){ // do something }, milliseconds)
// 一次性任務 var timer = setTimeout(function(){ // do something }, milliseconds)unix crontab 能解決問題嗎?
crontab 并不能精確到秒,crontab 的最小粒度是分,即當第一位是「*/1」時,即最小單位是每分鐘執行,(不排除你們有奇淫技巧可以做到秒級控制的)。unix 本身支持強大的定時任務管理 crontab,定時的格式也是強大得令人驚嘆。
* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) │ │ │ │ └───── month (1 - 12) │ │ │ └────────── day of month (1 - 31) │ │ └─────────────── hour (0 - 23) │ └──────────────────── minute (0 - 59) └───────────────────────── second (0 - 59, optional)
1)Cron 表達式的格式:秒 分 時 日 月 周 年 (可選)。
字段名 允許的值 允許的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小時 0-23 , - * /
日 1-31 , - * ? / L W C
月 1-12 or JAN-DEC , - * /
周幾 1-7 or SUN-SAT , - * ? / L C #
年 (可選字段) empty, 1970-2099 , - * /
「?」字符:表示不確定的值
「,」字符:指定數個值
「-」字符:指定一個值的范圍
「/」字符:指定一個值的增加幅度。n/m 表示從 n 開始,每次增加 m
「L」字符:用在日表示一個月中的最后一天,用在周表示該月最后一個星期 X
「W」字符:指定離給定日期最近的工作日 (周一到周五)
「#」字符:表示該月第幾個周 X。6#3 表示該月第 3 個周五
Cron 表達式范例:每隔 5 秒執行一次:/5 * ?
每隔 1 分鐘執行一次:0 /1 ?
每天 23 點執行一次:0 0 23 ?
每天凌晨 1 點執行一次:0 0 1 ?
每月 1 號凌晨 1 點執行一次:0 0 1 1 * ?
每月最后一天 23 點執行一次:0 0 23 L * ?
每周星期天凌晨 1 點實行一次:0 0 1 ? * L
在 26 分、29 分、33 分執行一次:0 26,29,33 * ?
每天的 0 點、13 點、18 點、21 點都執行一次:0 0 0,13,18,21 ?
每種開發語言都提供了 crontab 的相關封裝,讓開發者調用起來得心應手。以 node 為例:
require("crontab").load(function(err, crontab) { // create with string expression var job = crontab.create("ls -la", "0 7 * * 1,2,3,4,5"); });
你在 github 搜索 crontab 能搜到主流語言的實現。
有個問題,定時器不準時!
setInterval 的回調函數并不是到時后立即執行,而是等系統計算資源空閑下來后才會執行。而下一次觸發時間則是在 setInterval 回調函數執行完畢之后才開始計時,所以如果 setInterval 內執行的計算過于耗時,或者有其他耗時任務在執行,setInterval 的計時會越來越不準, 延遲很厲害。crontab 也是同樣的原理。
var startTime = new Date().getTime(); var count = 0; //耗時任務 setInterval(function(){ var i = 0; while(i++ < 100000000); }, 0); setInterval(function(){ count++; console.log(new Date().getTime() - (startTime + count * 1000)); }, 1000);
結果
126 176 163 112 109 107 203 189 170
當然,不排除你們有奇淫技巧可以做到秒級控制的。
成千上萬定時任務時怎么管理?Crontab 存在任務上限(其實我也不知道上限是多少,知道的麻煩告訴我),任務的同步、備份管理都比較麻煩,也會有比較多的并發問題需要處理。在分布式系統中,多帶帶去部署一個定時任務機器也是可行的。不過任務調度、定時結束通知客戶端也需要蠻多工作量的。
unix 的 crontab 不再是我們的第一選擇,每種編程可能都有定時任務管理的相關框架。比如 java 的 Quartz,Python 的 APScheduler。nodejs 的 node-schedule。但是這些東西是否能真的滿足你的需求呢?
思路和實現So,我們需要一個定時任務管理平臺。
目標
業務方可以定義定時時間、時間結束的觸發任務
業務方可以更新或者刪除已經發布的定時任務
定時任務管理平臺統一接收和調度任務
主要解決兩個問題:
設置準確的定時時間
時間結束觸發客戶端,不能重復消費
redis 在 2.8.X 版本可以開啟了鍵空間通知,更多相關請移步 Redis Keyspace Notifications。(默認不開啟,3.x 版本好像就失效了。),redis 支持的很多鍵空間事件,比如:DEL,RENAME,EXPIRE等等,redis 本身可以定義某個鍵的過期時間,ttl key。
這個值正好用來設置為定時任務的時間。更多相關請移步 Redis Keyspace Notifications。如果客戶端訂閱了某種規則的鍵通知,比如過期,那么在某個鍵過期的時候就會收到一個通知,這個事件就是定時結束,可以告訴業務機可以開啟任務了。
可如果有多個 redis 客戶端訂閱了某個鍵的過期時間,那么任務還是會被觸發很多次。 因為每個客戶端
都是平等的,你能訂閱,我同樣可以訂閱。解決辦法就是 生產者和消費者模式。同一個過期消息只能被消費一次。
把所有的定時任務按照定時開啟的時間倒序排列,存入 sorted Sets , 把時間設置為 score。這樣就會形成一個按照時間排好序的集合,可以按照時間先后依次取出所有的任務,需要新增和修改任務,也是可以通過 redis 的命令實現的。
定時管理服務器每 1000ms 去取 sorted sets 頂部的數據,如果獲取到的 task 離觸發小于 1s,那么就可以執行 pop() 操作,表示這個任務開始被調度執行,因為 redis 的 pop() 是原子性的,同一個 task 永遠只會被消費一次。這樣就解決了 redis 鍵空間通知會被重復消費的問題。
偽代碼如下:
var taskSorts = new Sets(task1, task2, task3); // 在 redis 中建立按時間排序的集合 // 每隔一秒執行一下操作, var newOne = taskSorts.zrank(-1); // 獲取到最快發生的任務 if(newOne.time < 1000){ // 如果滿足消費條件 newOne = taskSorts.pop(); // 消費該任務,重復此循環,繼續消費下一個任務 setTimeout(function(){ // dosomething }, newOne.time) }任務觸發
任務的提交和觸發都應該在業務方完成。定時任務管理平臺只是幫助管理和調度任務。在定義的任務里面定義好任務執行的回調參數和接口。
客戶端定義任務的時候,同時注冊好定時結束的回調接口,或者應該在項目啟動的時候,就注冊好所有回調的接口。因為同一個業務的 A 機器提交了任務,觸發的時候可能 A 機器下線了,只能定時任務平臺只能去觸發業務 A 的 B 機器了。
引入跨服務遠程調用。業務和定時任務管理平臺可能不在同一個機器,可能分布在不同的 ip。聽起來很復雜,實際上跨語言的調用調用方式有很多,比如 REST API、消息隊列、RPC。我的團隊選擇了 Thrift(Facebook 開源的,跨語言的,現在共享給了 Apache 基金)。以上的方式都可以實現任務只被觸發了一次,遠程通知給客戶端(任務注冊方)。
成品 -- nodejs 的實現 cron-redishttps://github.com/MZMonster/cron-redis
主要依賴 bull 實現了任務隊列的管理功能實現的定時任務管理工具。
demo:
// 就這樣定義,3 秒鐘之后,hello 函數將被執行。 function hello (x, y){ console.log(new Date()); console.log(x + " + "+ y +" = %s", x+y); } // 我是一個任務 var task1 = { method: hello.name, // 任務回調的函數 params: [2, 3], // 任務執行的參數 rule: moment().add(3, "s").toDate() // 任務執行間隔,支持 crontab 格式 } queue.register(hello) queue.publish(task1);
如果你要求不高,unix 自帶的 crontab 也足夠你折騰了。使用 redis 來實現定時也是一種極好的思路,cron-redis 值得你去試一試。
該庫只是一個定時任務的庫,實際上可以通過以上的思路實現微服務————定時任務管理平臺。通過 cron-redis 組合遠程服務調用 thrift、服務的注冊發現工具 zookeeper,定時任務管理平臺分分鐘就被搭建了(等我下一篇文章吧,分分鐘搭建微服務)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79079.html
摘要:在軟件項目中,定時器也被應用到了各方各面,本文將從項目入手,講述定時器,本文的例子都以為例。定時器總類定時器有兩種對應重復任務和一次性任務。 在大規模分布式系統中,每個業務都可能是集群,每個業務機都會產生定時任務,不同的業務會有不同的任務管理需求,統一的任務調度和管理變得非常有必要。 定時如何準確,大量的定時被同時觸發怎么辦? 定時結束的時候,怎么通知業務機去處理呢? 某臺業務機下線...
摘要:微服務架構概述應用架構的發展應用是可獨立運行的程序代碼,提供相對完善的業務功能。阿里開源的是的典型實現。它目前由官方開發維護,基于開發,提供一套完整的微服務解決方案。 微服務與Spring Cloud 隨著互聯網的快速發展, 云計算近十年也得到蓬勃發展, 企業的IT環境和IT架構也逐漸在發生變革,從過去的單體應用架構發展為至今廣泛流行的微服務架構。 微服務是一種架構風格, 能給軟件應用...
摘要:是由淘寶網發起的服務器項目。回源監控是內容分發網絡的簡稱,其分發的內容來自用戶源站,負責回源的模塊是最重要組成部分之一,使跨越單機的限制,完成網絡數據的接收處理和轉發。這部分主要介紹的一些調試技巧和回源資源監控的內容,以及相應的實例分享。 摘要: Tengine是由淘寶網發起的Web服務器項目。它在Nginx的基礎上,針對大訪問量網站的需求,提供更強大的流量負載均衡能力、全站HTTPS...
摘要:網站都是從小網站一步一步發展為大型網站的,而這之中的挑戰主要來自于龐大的用戶安全環境惡劣高并發的訪問和海量的數據,任何簡單的業務處理,一旦需要處理數以計的數據和面對數以億計的用戶時,問題就會變的很棘手下面我們就來說說這個演變過程初始階段大型 網站都是從小網站一步一步發展為大型網站的,而這之中的挑戰主要來自于龐大的用戶、安全環境惡劣、高并發的訪問和海量的數據,任何簡單的業務處理,一旦需要...
閱讀 1588·2021-09-23 11:21
閱讀 2352·2021-09-07 10:13
閱讀 840·2021-09-02 10:19
閱讀 1134·2019-08-30 15:44
閱讀 1728·2019-08-30 13:18
閱讀 1918·2019-08-30 11:15
閱讀 1111·2019-08-29 17:17
閱讀 2021·2019-08-29 15:31