在所有的服务器编程当中,定时任务永远是一个不可或缺的需求。
最直接的需求就是,每天凌晨0点0分的时候总是有一大堆的各种精力重置。
怎么来设计这个接口呢,想了几个方案:

  • 每秒触发
  • 每分钟触发
  • 每整点触发
  • 每天触发
  • 每个月触发

oh no!不靠谱啊,如果这接口真设计成这样,得有多烂,灵光一现,unix下的crontab表达式非常完美的解决了这个问题。

附上crontab表达式的语法说明如下:

crontab特殊的符号说明:

"*"代表所有的取值范围内的数字。特别要注意哦!
"/"代表每的意思,如"*/5"表示每5个单位
"-"代表从某个数字到某个数字
","分散的数字

crontab文件的使用示例:

30 21 * * * 表示每晚的21:30 
45 4 1,10,22 * * 表示每月1、10、22日的4 : 45
10 1 * * 6,0 表示每周六、周日的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点

看起来很复杂的样子,但其实够用就好,我们也不需要实现全部特性。

  • 实现一个毫秒级别的定时器Update
  • 根据这个update函数实现一个秒级别定时器
  • 然后每秒取得自然时间与表达式中 分、时、几号、月份、星期几 分别匹配就可以实现了
  • 由于定时器除了增加以外,可能还需要一个删除功能,那就再提供一个定时器命名的功能,用于增删改查定时器是本身
  • 再加个测试函数。。完美

直接上代码:

--------------------------------------------
--任何一个记录产生一个实例
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  下载代码

参考资料:

http://www.cise.ufl.edu/~cop4600/cgi-bin/lxr/http/source.cgi/commands/simple/cron.c

https://github.com/kikito/cron.lua

lua定时器与定时任务的接口设计的更多相关文章

  1. Java生鲜电商平台-定时器,定时任务quartz的设计与架构

    Java生鲜电商平台-定时器,定时任务quartz的设计与架构 说明:任何业务有时候需要系统在某个定点的时刻执行某些任务,比如:凌晨2点统计昨天的报表,早上6点抽取用户下单的佣金. 对于Java开源生 ...

  2. Java开发笔记(九十九)定时器与定时任务

    前面介绍了线程的几种运行方式,不管哪种方式,一旦调用了线程实例的start方法,都会立即启动线程的事务处理.然而某些业务场景在事务执行时间方面有特殊需求,例如期望延迟若干时间之后才开始事务运行,又如期 ...

  3. 数据仓储之DLL层接口设计

    一.接口设计 1.1. IBaseRepository.cs public interface IBaseRepository<T> { T Add(T entity); bool Upd ...

  4. RESTful接口设计原则/最佳实践(学习笔记)

    RESTful接口设计原则/最佳实践(学习笔记) 原文地址:http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api 1 ...

  5. Web API接口设计经验总结

    在Web API接口的开发过程中,我们可能会碰到各种各样的问题,我在前面两篇随笔<Web API应用架构在Winform混合框架中的应用(1)>.<Web API应用架构在Winfo ...

  6. Verilog学习笔记简单功能实现(七)...............接口设计(并行输入串行输出)

    利用状态机实现比较复杂的接口设计: 这是一个将并行数据转换为串行输出的变换器,利用双向总线输出.这是由EEPROM读写器的缩减得到的,首先对I2C总线特征介绍: I2C总线(inter integra ...

  7. 优秀的API接口设计原则及方法(转)

    一旦API发生变化,就可能对相关的调用者带来巨大的代价,用户需要排查所有调用的代码,需要调整所有与之相关的部分,这些工作对他们来说都是额外的.如果辛辛苦苦完成这些以后,还发现了相关的bug,那对用户的 ...

  8. atitit.基于http json api 接口设计 最佳实践 总结o7

    atitit.基于http  json  api 接口设计 最佳实践 总结o7 1. 需求:::服务器and android 端接口通讯 2 2. 接口开发的要点 2 2.1. 普通参数 meth,p ...

  9. App接口设计

    关于APP接口设计 http://blog.csdn.net/gebitan505/article/details/37924711/

随机推荐

  1. 2014 年10个最佳的PHP图像操作库--留着有用

    Thomas Boutell 以及众多的开发者创造了以GD图形库闻名的一个图形软件库,用于动态的图形计算. GD提供了对于诸如C, Perl, Python, PHP, OCaml等等诸多编程语言的支 ...

  2. iOS中CollectionView由于多次点击造成错误的解决方案

    iOS中CollectionCiew由于多次点击,会给程序造成错误. 这个时候,我们可以用过手势类来进行判断和过滤. 但是,有一个快捷的解决方法,那就是给用户响应增加延时操作. 具体代码如下: [co ...

  3. Mysql权限控制 - 允许用户远程连接

    Mysql为了安全性,在默认情况下用户只允许在本地登录,可是在有此情况下,还是需要使用用户进行远程连接,因此为了使其可以远程需要进行如下操作: 一.允许root用户在任何地方进行远程登录,并具有所有库 ...

  4. C#综合揭秘——细说多线程

    一.线程的定义  1. 1 进程.应用程序域与线程的关系 进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源.进程之间是相对独立的,一个进程无法访问另一个进程 ...

  5. ueditor 编辑器再thinkphp中使用 解决转义问题

    在前台common.php文件中加入下面的函数就可以解决了 <?php //取消thinkphp里面的转义 if (get_magic_quotes_gpc()) { function stri ...

  6. python time模块详解(转)

    python 的内嵌time模板翻译及说明  一.简介   time模块提供各种操作时间的函数  说明:一般有两种表示时间的方式:       第一种是时间戳的方式(相对于1970.1.1 00:00 ...

  7. spring mvc使用的一些注意事项

    一天不进步,就是退步! 1.静态文件的处理 可以使用<mvc:resources mapping="/static/**" location="/WEB-INF/s ...

  8. 【PHP代码审计】 那些年我们一起挖掘SQL注入 - 7.全局防护盲点的总结上篇

    0x01 背景 现在的WEB应用对SQL注入的防护基本都是判断GPC是否开启,然后使用addlashes函数对单引号等特殊字符进行转义.但仅仅使用这样的防护是存在很多盲点的,比如最经典的整型参数传递, ...

  9. 关于 ArtifactTransferException: Failure to transfer

    eclipse 在导入maven project后,pom.xml有可能出现这种错误. 这里update maven project解决了:右键点击Maven项目->Maven->Upda ...

  10. 多线程和并发管理 .NET多线程服务

    线程相关静态变量 默认静态变量应用程序域所有线程可见.如果静态变量需要在线程间共享,同步访问也就必然了. 线程相关静态变量保证线程安全,同一时间只有一个线程可访问,且每个线程都有该静态变量的拷贝. p ...