使用 destoon 框架(6.0)过程中发现,存在「计划任务」未按时执行的问题。比如 2 小时订单未支付自动关闭的任务,通过后台单次调用成功,但并没有按照任务设置中的每半小时执行一次。上次运行时间和下次运行时间根本对不上,通过测试未支付订单也确未自动关闭。

使用过的 ci 和 tp 的定时任务(同计划任务),运行脚本部分与 destoon 中的形式基本一致。ci 和 tp 使用 linux 系统计划任务工具 crontab 来实现定时执行,这并不难理解。但使用的 destoon 系统安装在 windows server 上,并且 destoon 的安装文档 里也没有标注对 cron 的额外配置。这很奇怪,不借助系统级别的 cron 计划任务,destoon 本身是怎么实现计划任务的呢?

通过对框架代码的追溯,找到了 destoon 执行计划任务的源文件 DT_ROOT.'/api/cron.inc.php'。以下为核心代码摘录:

$result = $db->query("SELECT * FROM {$DT_PRE}cron WHERE nexttime<$DT_TIME ORDER BY itemid");
while($cron = $db->fetch_array($result)) {
    if($cron['status']) continue;
    include DT_ROOT.'/api/cron/'.$cron['name'].'.inc.php';
    $nexttime = nexttime($cron['schedule'], $DT_TIME);
    $db->query("UPDATE {$DT_PRE}cron SET lasttime=$DT_TIME,nexttime=$nexttime WHERE itemid=$cron[itemid]");
}

查询出下次运行时间过了的计划任务,一一运行并更新下次运行时间。

这是一个多计划任务运行的入口文件,只需要确保这个文件在计划任务的最小刻度定时执行就没有问题。但文件首行代码 defined('IN_DESTOON') or exit('Access Denied'); 决定了该文件只能内部调用,不能直接运行。这点证实了 destoon 没有借助系统计划任务实现定时执行。

通过对 cron.inc.php 引用进行追溯,发现了前后台调用计划任务的机制。前台通过 page.js 中的 show_task() 方法引入 api/task.js.php 文件,而 task.js.php 直接引入了 cron.inc.phppage.js 在前台 header 模板中引入,也就是说,只要调用前端页面,就会执行一次 cron.inc.php。而后台通过 ?action=cron 调用,这个请求在后台 side(新版本 7.0 是在 left)模板中引入,也就是每次登录后台或者刷新整个后台页面,会执行一次 cron.inc.php

后台登录或刷新的几率很小,所以 destoon 的计划任务基本依赖于前台页面的实现。这就会存在一个问题,如果前台访问频次不够多,那么计划任务的执行就可能被拖延,甚至不执行;而当前台访问频次过高时(每秒几十次),这又会反复的去查询数据库并做判断,可能会造成重复执行计划任务(这问题不大,主要是前面不能定时执行这一点)。这显然不能满足企业对计划任务的期望。

当然,这只是我个人对代码的解析,可能新版本的 destoon 会有更改措施也不一定。