应用场景:很多时候,我们的客户端希望自行订制一些即时执行的简单定时任务,而这是unix系统的crontad实现起来比较困难的,这时候,这种情况下,杀鸡并不需要什么宰牛刀啊,只是一个简单的定时任务而已,有没有简单的办法可以实现呢?当然有!
下面,我们通过一些小技巧来实现定时任务的生成和控制。代码如下:
crontab_config.php:作为一个任务控制文件 * 您可以将这里看做是每一个任务生成时的数据库记录集合 */
return [ 's1' => [
'continue' => 1,//总开关 'expire' => 1800,//允许任务最大自主运行时间(s) 'start_time' => strtotime('2017-06-23 14:10:00'),//开始运行时间,这里用strtotime是为了更直观而已 'sleep_time' => 15,//脚本睡眠时间(s) 'time_limit' => 0,//设置当前进程运行时间(s),为0则无(计算量较大时) 'memory_limit' => '128M'//设置php.ini中运行内存的值(M),建议大于128M,小于2048M ], 's2' => [ 'continue' => 0, 'expire' => 3600, 'start_time' => strtotime('2017-06-23 08:30:00'), 'sleep_time' => 15, 'time_limit' => 30, 'memory_limit' => '128M' ] #...... ];
php_crontab.php:任务执行文件 //serial_no:作为一个获取控制脚本记录的一个索引 $serial_no = $_GET['serial_no'];
if ($config[$serial_no]['continue'] != 1) exit(); if (($config[$serial_no]['expire'] + $config[$serial_no]['start_time']) < time()) exit(); // 设置为0可以让程序无的执行下去 set_time_limit($config[$serial_no]['time_limit']);
// 设置内存,不建议大于2048M
ini_set('memory_limit', $config[$serial_no]['memory_limit']); $mainObj = new main();
$no = $_GET['no']?(intval($_GET['no'])+1):1; $mainObj->doSomething($no);//执行主程序
sleep($config[$serial_no]['sleep_time']);//睡眠
$url = \"http://localhost/php_crontab.php?serial_no={$serial_no}&no={$no}\"; //继续执行下一次脚本(接口的方式,方便断开连接) $mainObj->http_get($url, 1000); exit(); /*
* 主程序部分 */
class main {
/**
* @param int no 执行次数 */ public function doSomething($no){
}
$log = date('Y-m-d H:i:s').\" 这是第{$no}次执行\\r\\n\";
file_put_contents('./php_crontab.log', $log, FILE_APPEND); return true;
/**
* GET 请求
* @param string url 访问链接
* @param int time_out 等待响应超时时间(ms) * @return string content */
public function http_get($url, $time_out=2000) { $oCurl = curl_init();
if (stripos($url, \"https://\") !== FALSE) {
curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1 }
curl_setopt($oCurl, CURLOPT_URL, $url);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1); //注意,毫秒超时一定要设置这个
curl_setopt($oCurl,CURLOPT_NOSIGNAL,1);
?>
//超时毫秒,cURL7.16.2中被加入。从PHP5.2.3起可使用
//如果接口超时响应,则断开与它的连接(远程接口将不受影响) curl_setopt($oCurl,CURLOPT_TIMEOUT_MS,$time_out); $sContent = curl_exec($oCurl); $aStatus = curl_getinfo($oCurl); curl_close($oCurl);
if (intval($aStatus[\"http_code\"]) == 200) { return $sContent; } else {
return false; } } }
现在,我们通过浏览器来访问:http://localhost/php_crontab.php?serial_no=s1 默认浏览在30s后会断开,但程序会按照crontab_config.php中s1的配置继续执行,直到1800s后会自动终止,运行之后,部分结果如下图:
可能很多小伙伴会有疑问:
1、为什么浏览器(browser)断开之后,程序会继续执行呢?
原因在于设置了ignore_user_abort(true),其可忽略客户端是否断开
2、是不是我们可以通过配置crontab_config.php的相关数组参数就可以控制脚本的执行和状态了
是的。在实际项目,我们的crontab_config.php中的数据可以是每次生成任务时,用户自行配置的记录,这样会更加灵活,如果有需要,还可以做成分布式的任务呢,那么,大量的计算就不再是问题啦
3、为什么要使用自定义的http_get()接口方式访问下一次的脚本呢,不可以使用file_get_contents直接访问吗?
当然可以使用file_get_contents;这里使用接口的方式是为了能够及时关闭本次超时
的响应,不需要作太长时间的等待,降低服务器压力,这样做,相对于直接使用
file_get_contents类的函数会更好一点
4、这个程序有什么缺点吗?
最大的缺点是,当链路不友好的时候,只要有一次接口请求没有到达服务端,那么,整
个任务就会被终止了。这时候,您可以通过进一步优化,写一些脚本来实时跟踪或者检测相应任务的状态,比如sokect就是一个不错的选择,当然,ajax轮询也可以,此处不再赘述,有兴趣的可以自行探讨一下php的socket部分
5、如果我想把定时任务设置成指定时间才开始执行的,可以吗?
当然可以。您只要简单地在Unix系统上再加上一个crontab任务定时执行就可以了,这个定时任务,您是用来检查和执行数据库中所有已生成但未执行的定时任务的,只要执行过一次的,咱们下次crontab查询的时候不再执行就可以了,因为一旦crontab触发一个自定义的任务之后,后面的就交由上面的程序去完成啦