ThinkPHP定时任务(二)
前面说了TP框架中定时任务实现,也对存在的问题和优势做了一个简单的分析。如果有兴趣看的朋友可以参看ThinkPHP定时任务(一)。这篇主要对基于传统定时任务crontab[守护进程],外加TP框架支持实现的基于TP的定时任务
动机
用过TP的朋友都知道,TP框架是一个由国人开发的很优秀且轻量的PHP Web框架。由于其丰富的文档和强大的用户群体,在国内有不错的保有量。记得之前项目之初选择框架的时候,朋友建议选择TP,给的建议就是用户群体大,便于招人。
TP在Web模式下获得除了不俗的成绩,但是在非CGI模式下,通常(CLI),缺罕有人用。究其原因,个人觉得,无外乎以下几点:
- 文档解释的比较少
- 对CLI模式本身支持的比较简单
- 有这样前后台执行需求的用户大多选择了laravel等国际框架
然而,我们的工程确实是用TP开发,为了保证代码结构统一,不用重复造轮子。就难免需要探索下TP框架下怎么实现后台任务的执行,如果这个问题解决了,再结合crontab就能找到我们想要的解决方案
TP框架的Mode
mode即是指应用模式,TP文档中是这么说明的:
应用模式提供了对核心框架进行改造的机会,可以让你的应用适应更多的环境和不同的要求。 每个应用模式有自己的模式定义文件,用于配置当前模式需要加载的核心文件和配置文件,以及别名定义、行为扩展定义等等。根据模式定义文件的定义位置和入口是否需要定义模式,可以分为显式应用模式和隐含应用模式。
而这里,我们要使用的就是TP3.2自带的api模式,要开启api模式,我们只需要做如下简单的配置
//cli模式
define('APP_MODE','api');
这个宏有默认值,如果我们没有定义的的话,会在应用的入口自动设置为我们的常用模式
defined('APP_MODE') or define('APP_MODE', 'common'); // 应用模式 默认为普通模式
关于api模式的配置文件,可以参考Think/mode/api.php
,里边有对内置api应用模式的基础配置,我们可以根据我们的需求进行改写,或者通过配置覆写系统配置。而我们常用的普通模式就是该目录下的common.php,大家可以对比进行学习
兼容配置
APP_PATH 配置
由于我们的应用是在普通模式下工作的,当我们在调用API模式时,肯定希望应用目录保持一致,这样就不用做任何代码的迁移和改动。在api模式中加入如下配置
// 定义应用目录,同样指向common模式下的app目录
define('APP_PATH','../Apps/');
APP_STATUS 配置
起先,本人以为截止上一步就完成了所有配置。但是在调试的过程中,发现数据库无法连接,最终定位到。APPS目录中的配置没有正常加载。这个过程是个比较长的调试过程,具体就不表了。查看源代码发现,common模式下的所有配置是在Think/Libary/Think/Dispatcher.class.php
中进行引入的,其中有如下代码,是完成MODULE
中配置的加载的,有兴趣的同学可以自己看看
// 当前应用状态对应的配置文件
if(APP_STATUS && is_file(MODULE_PATH.'Conf/'.APP_STATUS.CONF_EXT))
C(load_config(MODULE_PATH.'Conf/'.APP_STATUS.CONF_EXT));
// 加载模块别名定义
if(is_file(MODULE_PATH.'Conf/alias.php'))
Think::addMap(include MODULE_PATH.'Conf/alias.php');
// 加载模块tags文件定义
if(is_file(MODULE_PATH.'Conf/tags.php'))
Hook::import(include MODULE_PATH.'Conf/tags.php');
// 加载模块函数文件
if(is_file(MODULE_PATH.'Common/function.php'))
include MODULE_PATH.'Common/function.php';
// 加载模块的扩展配置文件
load_ext_file(MODULE_PATH);
同样找到Think/Mode/Api/Dispatcher.class.php
,在合适的时机添加上述加载代码(本人TP3.2是在146行开始加的),这样后就可加载MODULE
中配置。
至此,所有的配置均已完成,下来我们来看看怎么操作。
目录结构
先看看我这边的目录结构吧,便于后续表述
PROJECT - APPS + module_1 + module_2 - Crons + crons.conf + curl_runloop.sh + daemon.sh + exec.php + runloop.sh + start.sh
Crons就是我们的定时任务目录,这里我着重说一下Crons目录下各个文件的用途吧。
- crons.conf 定时任务的配置文件,配置了要执行什么定时任务
- curl_runloop.sh 通过url调用时的runloop脚本
- daemon.sh 守护进程
- runloop.sh 直接PHP调用的runloop脚本
- exec.php命令执行脚本
如何执行
有了上面的铺垫,启动脚本其实很简单,如下:
php exec.php script_path
这里做个简单的说明,比如我们之前的访问的URL如下:
http://xxx.com/abc/controller/action
我们的script_path的值应该传:
abc/controller/action
这样即可调用的我们的对应方法,完成后台执行
时间控制
定时任务肯定逃不过计划执行时间,轮训时间。这里我从controller层做了些许限制。当然也可以在runloop中去做。将配置信息写在crons.conf中。进行配置既可
建立一个用于执行定时任务的基类来统一处理调度和并发逻辑。需要能别后台调用起的定时任务需要继承该基类,完成基础功能。主要涉及的一下几个地方。
// cronbaseclass 实现
// 多少时间执行一次
protected $interval = 60;
// 检测是否有权限调用
private function check() {
if (PHP_SAPI != 'cli' && !in_array(get_client_ip(),C('CRONS_BIND_IP'))) {
exit('what a fucking day!');
}
}
// 入口方法
public function index() {
set_time_limit(0);
$exe_time = $this->cron_config(CONTROLLER_NAME.ACTION_NAME);
if ($exe_time < time()) {
$this->update_cron_config(CONTROLLER_NAME.ACTION_NAME,$this->interval + time());
$lock_file = RUNTIME_PATH.md5(CONTROLLER_NAME.ACTION_NAME);
if (empty(S($lock_file))) {
S($lock_file,1,600);
$this->run();
S($lock_file,null);
}
}
}
// 子类逻辑执行方法
protected function run() {}
runloop
runloop 其实很简单,就是一个死循环,然后去读取配置文件,通过php exec.php xxx
去执行脚本任务。特别需要注意的daemon.sh
脚本,是我们统一守护进程。crontab中需要进行配置如下:
# 守护进程守护runloop.sh进程,去执行release环境[APP_STATUS]
* * * * * daemon.sh runloop.sh release
结语
到现在,我们所有的配置和执行方案已经分析完成。根据这些配置,大家可以灵活的组织各种定时情况。
其实每个框架都有自身的强大之处和不足,只要根据它的特性和设计方案出发,一般都能找到合适的方案。自己碰到的问题,相信框架的设计者也有所涉及。本文其实就是在TP的框架中做了一些小的改动来支持定时任务。
附录
crons.conf内容格式
Agreement/CCallback
Agreement/Achive
Agreement/SynchronizeStatus