PHP实现执行定时任务的几种思路详解

 

 

 

 

 

 

 

 

 

 

HP自己是没有定时功能的,PHP也不能多线程。PHP的定时任务功能必须经过和其余工具结合才能实现,例如WordPress内置了wp-cron的功能,很厉害。本文,咱们就来深刻的解析几种常见的php定时任务的思路。php

Linux服务器上使用CronTab定时执行php

咱们先从相对比较复杂的服务器执行php谈起。服务器上安装了php,就能够执行php文件,不管是否安装了nginx或Apache这样的服务器环境软件。而Linux中,使用命令行,用CronTab来定时任务,又是绝佳的选择,并且也是效率最高的选择。linux

首先,进入命令行模式。做为服务器的linux通常都默认进入命令行模式的,固然,咱们管理服务器也通常经过putty等工具远程链接到服务器,为了方便,咱们用root用户登陆。在命令行中键入:nginx

crontab -e

以后就会打开一个文件,而且是非编辑状态,则是vi的编辑界面,经过敲键盘上的i,进入编辑模式,就能够编辑内容。这个文件中的每一行就是一个定时任务,咱们新建一行,就是新建一条定时任务(固然是指这一行内按照必定的格式进行书写)。咱们如今来举个例子,增长一行,内容以下:git

00 * * * * lynx -dump https://www.yourdomain.com/script.php

这是什么意思呢?实际上上面这一行由两部分组成,前面一部分是时间,后面一部分是操做内容。例如上面这个,github

00 * * * *

就是指当当前时间的分钟数为00时,执行该定时任务。时间部分由5个时间参数组成,分别是:web

分 时 日 月 周

第1列表示分钟1~59 每分钟用或者 */1表示,/n表示每n分钟,例如*/8就是每8分钟的意思,下面也是类推
第2列表示小时1~23(0表示0点)
第3列表示日期1~31
第4列表示月份1~12
第5列标识号星期0~6(0表示星期天)shell

整个句子的后面部分就是操做的具体内容。数据库

lynx -dump https://www.yourdomain.com/script.php

意思就是说经过lynx访问这个url。咱们在使用中主要用到lynx、curl、wget来实现对url的远程访问,而若是要提升效率,直接用php去执行本地php文件是最佳选择,例如:编程

00 */2 * * * /usr/local/bin/php /home/www/script.php

这条语句就能够在每2小时的0分钟,经过linux内部php环境执行script.php,注意,这里可不是经过url访问,经过服务器环境来执行哦,而是直接执行,由于绕过了服务器环境,因此效率固然要高不少。segmentfault

好了,已经添加了几条须要的定时任务了吧。点击键盘上的Esc键,输入“:wq”回车,这样就保存了设置的定时任务,屏幕上也能看到提示建立了新的定时任务。接下来就是好好写你的script.php了。

关于CronTab的更多用法这里就不介绍了,若是你想更灵活的使用这个定时任务功能,应该本身再去深刻学习一下crontab。

Windows服务器上使用bat定时执行php

windows上和linux上有一个相似的cmd和bat文件,bat文件相似于shell文件,执行这个bat文件,就至关于依次执行里面的命令(固然,还能够经过逻辑来实现编程),因此,咱们能够利用bat命令文件在windows服务器上面实现PHP定时任务。实际上在windows上定时任务,和linux上道理是同样的,只不过方法和途径不一样。好了下面开始。

首先,在一个你以为比较适当的位置建立一个cron.bat文件,而后用文本编辑器打开它(记事本均可以),在里面写上这样的内容:

D:\php\php.exe -q D:\website\test.php

这句话的意思就是,使用php.exe去执行test.php这个php文件,和上面的contab同样,绕过了服务器环境,执行效率也比较高。写好以后,点击保存,关闭编辑器。

接下来就是设置定时任务来运行cron.bat。依次打开:“开始–>控制面板–>任务计划–>添加任务计划”,在打开的界面中设置定时任务的时间、密码,经过选择,把cron.bat挂载进去。肯定,这样一个定时任务就创建好了,在这个定时任务上右键,运行,这个定时任务就开始执行了,到点时,就会运行cron.bat处理,cron.bat再去执行php。

非自有服务器(虚拟主机)上实现php定时任务

若是站长没有本身的服务器,而是租用虚拟主机,就没法进入服务器系统进行上述操做。这个时候应该如何进行php定时任务呢?其实方法又有多个。

使用ignore_user_abort(true)和sleep死循环

在一个php文档的开头直接来一句:

ignore_user_abort(true);

这时,经过url访问这个php的时候,即便用户把浏览器关掉(断开链接),php也会在服务器上继续执行。利用这个特性,咱们能够实现很是牛的功能,也就是经过它来实现定时任务的激活,激活以后就随便它本身怎么办了,实际上就有点相似于后台任务。

而sleep(n)则是指当程序执行到这里时,暂时不往下执行,而是休息n秒钟。若是你访问这个php,就会发现页面起码要加载n秒钟。实际上,这种长时间等待的行为是比较消耗资源的,不能大量使用。

那么定时任务到底怎么实现呢?使用下面的代码便可实现:

<?php

ignore_user_abort(true);
set_time_limit(0);
date_default_timezone_set('PRC'); // 切换到中国的时间

$run_time = strtotime('+1 day'); // 定时任务第一次执行的时间是明天的这个时候
$interval = 3600*12; // 每12个小时执行一次

if(!file_exists(dirname(__FILE__).'/cron-run')) exit(); // 在目录下存放一个cron-run文件,若是这个文件不存在,说明已经在执行过程当中了,该任务就不能再激活,执行第二次,不然这个文件被屡次访问的话,服务器就要崩溃掉了

do {
  if(!file_exists(dirname(__FILE__).'/cron-switch')) break; // 若是不存在cron-switch这个文件,就中止执行,这是一个开关的做用
  $gmt_time = microtime(true); // 当前的运行时间,精确到0.0001秒
  $loop = isset($loop) && $loop ? $loop : $run_time - $gmt_time; // 这里处理是为了肯定还要等多久才开始第一次执行任务,$loop就是要等多久才执行的时间间隔
  $loop = $loop > 0 ? $loop : 0;
  if(!$loop) break; // 若是循环的间隔为零,则中止
  sleep($loop); 
  // ...
  // 执行某些代码
  // ...
  @unlink(dirname(__FILE__).'/cron-run'); // 这里就是经过删除cron-run来告诉程序,这个定时任务已经在执行过程当中,不能再执行一个新的一样的任务
  $loop = $interval;
} while(true);

经过执行上面这段php代码,便可实现定时任务,直到你删除cron-switch文件,这个任务才会中止。

可是有一个问题,也就是若是用户直接访问这个php,实际上没有任何做用,页面也会停在这个地方,一直处于加载状态,有没有一种办法能够消除这种影响呢?fsockopen帮咱们解决了这个问题。

fsockopen能够实如今请求访问某个文件时,没必要得到返回结果就继续往下执行程序,这是和curl一般用法不同的地方,咱们在使用curl访问网页时,必定要等curl加载完网页后,才会执行curl后面的代码,虽然实际上curl也能够实现“非阻塞式”的请求,可是比fsockopen复杂的多,因此咱们优先选择fsockopen,fsockopen能够在规定的时间内,好比1秒钟之内,完成对访问路径发出请求,完成以后就无论这个路径是否返回内容了,它的任务就到这里结束,能够继续往下执行程序了。利用这个特性,咱们在正常的程序流中加入fsockopen,对上面咱们建立的这个定时任务php的地址发出请求,便可让定时任务在后台执行。若是上面这个php的url地址是www.yourdomain.com/script.php,那么咱们在编程中,能够这样:

// ...
// 正常的php执行程序
// ..

// 远程请求(不获取内容)函数,下面能够反复使用
function _sock($url) {
  $host = parse_url($url,PHP_URL_HOST);
  $port = parse_url($url,PHP_URL_PORT);
  $port = $port ? $port : 80;
  $scheme = parse_url($url,PHP_URL_SCHEME);
  $path = parse_url($url,PHP_URL_PATH);
  $query = parse_url($url,PHP_URL_QUERY);
  if($query) $path .= '?'.$query;
  if($scheme == 'https') {
    $host = 'ssl://'.$host;
  }

  $fp = fsockopen($host,$port,$error_code,$error_msg,1);
  if(!$fp) {
    return array('error_code' => $error_code,'error_msg' => $error_msg);
  }
  else {
    stream_set_blocking($fp,true);//开启了手册上说的非阻塞模式
    stream_set_timeout($fp,1);//设置超时
    $header = "GET $path HTTP/1.1\r\n";
    $header.="Host: $host\r\n";
    $header.="Connection: close\r\n\r\n";//长链接关闭
    fwrite($fp, $header);
    usleep(1000); // 这一句也是关键,若是没有这延时,可能在nginx服务器上就没法执行成功
    fclose($fp);
    return array('error_code' => 0);
  }
}

_sock('www.yourdomain.com/script.php');

// ...
// 继续执行其余动做
// ..

把这段代码加入到某个定时任务提交结果程序中,在设置好时间后,提交,而后执行上面这个代码,就能够激活该定时任务,并且对于提交的这个用户而言,没有任何页面上的堵塞感。

借用用户的访问行为来执行某些延迟任务

可是上面使用sleep来实现定时任务,是效率很低的一种方案。咱们但愿不要使用这种方式来执行,这样的话就能够解决效率问题。咱们借用用户访问行为来执行任务。用户对网站的访问实际上是一个很是丰富的行为资源,包括搜索引擎蜘蛛对网站的访问,均可以算做这个类型。在用户访问网站时,内部加一个动做,去检查任务列表中是否存在没有被执行的任务,若是存在,就将这个任务执行。对于用户而言,利用上面所说的fsockopen,根本感受不到本身的访问居然还作出了这样的贡献。可是这种访问的缺点就是访问很不规律,好比你但愿在凌晨2点执行某项任务,可是这个时间段很是倒霉,没有用户或任何行为到达你的网站,直到早上6点才有一个新访问。这就致使你本来打算2点执行的任务,到6点才被执行。

这里涉及到一个定时任务列表,也就是说你须要有一个列表来记录全部任务的时间、执行什么内容。通常来讲,不少系统会采用数据库来记录这些任务列表,好比wordpress就是这样作的。我则利用文件读写特性,提供了托管在github上的开源项目php-cron,你能够去看看。总之,若是你想要管理多个定时任务,靠上面的单个php是没法合理布局的,必须想办法构建一个schedules列表。因为这里面的逻辑比较复杂,就再也不详细阐述,咱们仅停留在思路层面上。

借用第三方定时任务跳板

很好玩的是,一些服务商提供了各类类型的定时任务,例如阿里云的ACE提供了单独的定时任务,你能够填写本身应用下的某个uri。百度云BCE提供了服务器监测功能,天天会按照必定的时间规律访问应用下的固定uri。相似的第三方平台上还有不少定时任务能够用。你彻底能够用这些第三方定时任务做为跳板,为你的网站定时任务服务。好比说,你能够在阿里云ACE上创建一个天天凌晨2点的定时任务,执行的uri是/cron.php。而后你建立一个cron.php,里面则采用fsockopen去访问你真正要执行某些任务的网站的url,例如上面的www.yourdomain.com/script.php,并且在cron.php中还能够访问多个url。而后把cron.php上传到你的ACE上面去,让ACE的定时任务去访问/cron.php,而后让cron.php去远程请求目标网站的定时任务脚本。

循环利用include包含文件(待验证)

php面向过程的特性使得其程序是从上往下执行的,利用这个特性,在咱们使用include某个文件时,就会执行被引入的文件,知道include的文件内程序执行完以后,再往下执行。若是咱们建立一个循环,再利用sleep,不断的include某个文件,使循环执行某段程序,则能够达到定时执行的目的。咱们再进一步,并非利用while(true)来实现循环,而是利用被include文件自己再include自身来实现循环,好比咱们建立一个do.php,它的内容以下:

if(...) exit(); // 经过某个开关来关闭执行

// ... 
// 执行某些程序
// ...

sleep($loop); // 这个$loop在include('do.php');以前赋值

include(dirname(__FILE__).'/do.php');

其实经过这种方法执行和while的思路也像。并且一样用到sleep,效率低。

PHP定时任务是一个很是有意思的东西,虽说实话,用系统的php.exe去直接执行php文件的效率更高,可是对于不少普通站长而言,虚拟主机是没法作到直接php执行原生程序的。本文仅提供一些解决的思路,我也仅仅是在学习中,有不少问题或表述都不正确,但愿你指出来;你能够经过本文的思路,开发出本身的一种解决方案,但愿你能将方案发布,并与我一块儿探讨。

 

转 :https://segmentfault.com/a/1190000002955509

相关文章
相关标签/搜索