在全部的服務器編程當中,定時任務永遠是一個不可或缺的需求。
最直接的需求就是,天天凌晨0點0分的時候老是有一大堆的各類精力重置。
怎麼來設計這個接口呢,想了幾個方案:javascript
oh no!不靠譜啊,若是這接口真設計成這樣,得有多爛,靈光一現,unix下的crontab表達式很是完美的解決了這個問題。java
附上crontab表達式的語法說明以下:git
crontab特殊的符號說明:
"*"表明全部的取值範圍內的數字。特別要注意哦!
"/"表明每的意思,如"*/5"表示每5個單位
"-"表明從某個數字到某個數字
","分散的數字github
crontab文件的使用示例:
30 21 * * * 表示每晚的21:30
45 4 1,10,22 * * 表示每個月一、十、22日的4 : 45
10 1 * * 6,0 表示每週6、週日的1 : 10
0,30 18-23 * * * 表示在天天18 : 00至23 : 00之間每隔30分鐘
0 23 * * 6 表示每星期六的11 : 00 pm
* */1 * * * 每一小時
* 23-7/1 * * * 晚上11點到早上7點之間,每隔一小時
* 8,13 * * 1-5 從週一到週五的上午8點和下午1點
0 11 4 * mon-wed 每個月的4號與每週一到週三的11點
0 4 1 jan * 一月一號的4點編程
看起來很複雜的樣子,但其實夠用就好,咱們也不須要實現所有特性。服務器
直接上代碼:函數
-------------------------------------------- --任何一個記錄產生一個實例 local Clock = {} local Clock_mt = {__index = Clock} local function __checkPositiveInteger(name, value) if type(value) ~= "number" or value < 0 then error(name .. " must be a positive number") end end --驗證是否可執行 local function __isCallable(callback) local tc = type(callback) if tc == 'function' then return true end if tc == 'table' then local mt = getmetatable(callback) return type(mt) == 'table' and type(mt.__call) == 'function' end return false end local function newClock(cid, name, time, callback, update, args) assert(time) assert(callback) assert(__isCallable(callback), "callback must be a function") return setmetatable({ cid = cid, name = name, time = time, callback = callback, args = args, running = 0, update = update }, Clock_mt) end function Clock:reset(running) running = running or 0 __checkPositiveInteger('running', running) self.running = running self.deleted = nil --若是已經刪除的,也要復活 end local function updateEveryClock(self, dt) __checkPositiveInteger('dt', dt) self.running = self.running + dt while self.running >= self.time do self.callback(unpack(self.args)) self.running = self.running - self.time end return false end local function updateAfterClock(self, dt) -- returns true if expired __checkPositiveInteger('dt', dt) if self.running >= self.time then return true end self.running = self.running + dt if self.running >= self.time then self.callback(unpack(self.args)) return true end return false end local function match( left, right ) if left == '*' then return true end --單整數的狀況 if 'number' == type(left) and left == right then return true end --範圍的狀況 形如 1-12/5,算了,先不支持這種每隔幾分鐘的這種特性吧 _,_,a,b = string.find(left, "(%d+)-(%d+)") if a and b then return (right >= tonumber(a) and right <= tonumber(b)) end --多選項的狀況 形如 1,2,3,4,5 --哎,luajit不支持gfind, --for d in string.gfind(left, "%d+") do --其實也能夠for i in string.gmatch(left,'(%d+)') do local pos = 0 for st,sp in function() return string.find(left, ',', pos, true) end do if tonumber(string.sub(left, pos, st - 1)) == right then return true end pos = sp + 1 end return tonumber(string.sub(left, pos)) == right end local function updateCrontab( self, dt ) local now = os.date('*t') local tm = self.time --print('updateCrontab/now:', now.min, now.hour, now.day, now.month, now.wday) --print('updateCrontab/tm', tm.mn, tm.hr, tm.day, tm.mon, tm.wkd) --print('match:',match(tm.mn, now.min), match(tm.hr, now.hour), match(tm.day, now.day), match(tm.mon, now.month), match(tm.wkd, now.wday)) if match(tm.mn, now.min) and match(tm.hr, now.hour) and match(tm.day, now.day) and match(tm.mon, now.month) and match(tm.wkd, now.wday) then --print('matching',self.name,self.callback,self.running) self.callback(unpack(self.args)) self.running = self.running + 1 end return false end --遍歷並執行全部的定時器 local function updateClockTables( tbl ) for i = #tbl, 1, -1 do local v = tbl[i] if v.deleted == true or v:update(1) then table.remove(tbl,i) end end end ---------------------------------------------------------- local crontab = {} crontab.__index = crontab function crontab.new( obj ) local obj = obj or {} setmetatable(obj, crontab) --執行一下構造函數 if obj.ctor then obj.ctor(obj) end return obj end function crontab:ctor( ) --全部的定時器 self._clocks = self._clocks or {} self._crons = self._crons or {} --累積的時間差 self._diff = self._diff or 0 --已命名的定時器,設置爲弱引用表 self._nameObj = {} setmetatable(self._nameObj, {__mode="k,v"}) --取得如今的秒數,延遲到整點分鐘的時候啓動一個定時 self:after("__delayUpdateCrontab", 60-os.time()%60, function ( ) --在整點分鐘的時候,每隔一分鐘執行一次 self:every("__updateCrontab", 60, function ( ) updateClockTables(self._crons) end) end) end function crontab:update( diff ) self._diff = self._diff + diff while self._diff >= 1000 do --TODO:這裏真讓人糾結,要不要支持累積時間偏差呢? self._diff = self._diff - 1000 --開始對全部的定時器心跳,若是返回true,則從列表中移除 updateClockTables(self._clocks) end end function crontab:remove( name ) if name and self._nameObj[name] then self._nameObj[name].deleted = true end end --經過判斷callback的真正位置,以及參數類型來支持可變參數 --返回值順序 number, string, number, function, args --總的有以下5種狀況 --1) cid,name,time,callback,args --2) name,cid,time,callback,args --3) name,time,callback,args --4) cid,time,callback,args --5) time,callback,args local function changeParamsName( p1, p2, p3, p4, p5 ) if __isCallable(p4) then if type(p1) == 'string' then return p2,p1,p3,p4,p5 else return p1,p2,p3,p4,p5 end elseif __isCallable(p3) then if type(p1) == 'string' then return nil,p1,p2,p3,p4 else return p1,nil,p2,p3,p4 end else return nil,nil,p1,p2,p3 end end function crontab:every( cid, name, time, callback, args ) --支持可變參數 cid, name, time, callback, args = changeParamsName(cid, name, time, callback,args) __checkPositiveInteger('time', time) local clock = newClock(cid, name, time, callback, updateEveryClock, args or {}) table.insert(self._clocks,clock) if name and name ~= '' then self._nameObj[name] = clock end return clock end function crontab:after( cid, name, time, callback, args ) cid, name, time, callback, args = changeParamsName(cid, name, time, callback,args) __checkPositiveInteger('time', time) local clock = newClock(cid, name, time, callback, updateAfterClock, args or {}) table.insert(self._clocks,clock) if name and name ~= '' then self._nameObj[name] = clock end return clock end --增長計劃任務,精度到達分鐘級別 --表達式:分鐘[0-59] 小時[0-23] 每個月的幾號[1-31] 月份[1-12] 星期幾[1-7] -- 星期天爲1, -- "*"表明全部的取值範圍內的數字 -- "-"表明從某個數字到某個數字 -- "/"表明每的意思,如"*/5"表示每5個單位,未實現 -- ","分散的數字 -- 如:"45 4-23/5 1,10,22 * *" function crontab:addCron(cid, name, crontab_str, callback, args ) cid, name, crontab_str, callback, args = changeParamsName(cid, name, crontab_str, callback, args) --print(cid, name, crontab_str, callback) local t = {} for v in string.gmatch(crontab_str,'[%w._/,%-*]+') do --若是能夠轉成整型直接轉了,等下直接對比 local i = tonumber(v) table.insert(t, i and i or v) end if table.getn(t) ~= 5 then return error(string.format('crontab string,[%s] error!',crontab_str)) end local time = {mn = t[1], hr = t[2], day = t[3], mon = t[4], wkd = t[5]} local clock = newClock(cid, name, time, callback, updateCrontab, args or {}) table.insert(self._crons,clock) if name and name ~= '' then self._nameObj[name] = clock end end return crontab
再看看測試代碼:測試
--傳說中的測試代碼 local function RunTests() -- the following calls are equivalent: local function printMessage(a ) print('Hello',a) end local cron = crontab.new() local c1 = cron:after( 5, printMessage) local c2 = cron:after( 5, print, {'Hello'}) c1:update(2) -- will print nothing, the action is not done yet c1:update(5) -- will print 'Hello' once c1:reset() -- reset the counter to 0 -- prints 'hey' 5 times and then prints 'hello' while not c1:update(1) do print('hey') end -- Create a periodical clock: local c3 = cron:every( 10, printMessage) c3:update(5) -- nothing (total time: 5) c3:update(4) -- nothing (total time: 9) c3:update(12) -- prints 'Hello' twice (total time is now 21) ------------------------------------- c1.deleted = true c2.deleted = true c3.deleted = true ------------------------------ --測試一下match print('----------------------------------') assert(match('*',14) == true) assert(match('12-15',14) == true) assert(match('18-21',14) == false) assert(match('18,21',14) == false) assert(match('18,21,14',14) == true) --加一個定時器1分鐘後執行 cron:update(1000) --加入一個定時器每分鐘執行 cron:addCron('每秒執行', '* * * * *', print, {'.......... cron'}) cron:update((60-os.time()%60)*1000) cron:update(30*1000) cron:update(31*1000) cron:update(1) cron:update(60*1000) --打印兩次 end
也能夠直接到 https://github.com/linbc/crontab.lua 下載代碼ui
參考資料:lua
http://www.cise.ufl.edu/~cop4600/cgi-bin/lxr/http/source.cgi/commands/simple/cron.c