您的位置:首页 > 编程语言 > PHP开发

[php] 实现执行定时任务的方法

2017-01-17 16:58 441 查看
原文链接

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

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

首先,进入命令行模式。作为服务器的 linux 一般都默认进入命令行模式的,当然,我们管理服务器也一般通过 putty 等工具远程连接到服务器,为了方便,我们用 root 用户登录。在命令行中键入:

crontab -e


之后就会打开一个文件,并且是非编辑状态,则是 vi 的编辑界面,通过敲键盘上的 i,进入编辑模式,就可以编辑内容。这个文件中的每一行就是一个定时任务,我们新建一行,就是新建一条定时任务(当然是指这一行内按照一定的格式进行书写)。我们现在来举个例子,增加一行,内容如下:

00 * * * * lynx -dump https://www.yourdomain.com/script.php[/code] 
这是什么意思呢?实际上上面这一行由两部分组成,前面一部分是时间,后面一部分是操作内容。例如上面这个,

00 * * * *


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

分 时 日 月 周


第 1 列表示分钟 1 ~ 59 每分钟用或者 * / 1 表示,/ n 表示每 n 分钟,例如 * / 8 就是每 8 分钟的意思,下面也是类推

第 2 列表示小时 1 ~ 23(0 表示 0 点)

第 3 列表示日期 1 ~ 31

第 4 列表示月份 1 ~ 12

第 5 列标识号星期 0 ~ 6(0 表示星期天)

整个句子的后面部分就是操作的具体内容。

lynx -dump https://www.yourdomain.com/script.php[/code] 
意思就是说通过 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 访问,通过服务器环境来执行哦,而是直接执行,因为绕过了服务器环境,所以效率当然要高很多。

好了,已经添加了几条需要的定时任务了吧。点击键盘上的 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 定时任务

使用 ignore_user_abort(true) 和 sleep 死循环

ignore_user_abort(true);


这时,通过 url 访问这个 php 的时候,即使用户把浏览器关掉(断开连接),php 也会在服务器上继续执行。利用这个特性,我们可以实现非常牛的功能,也就是通过它来实现定时任务的激活,激活之后就随便它自己怎么办了,实际上就有点类似于后台任务。

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

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

ignore_user_abort(true);
set_time_limit(0);
date_default_timezone_set('PRC');
// 切换到中国的时间
$run_time = strtotime('+1 day');
// 定时任务第一次执行的时间是明天的这个时候
// 每 12 个小时执行一次
$interval = 3600*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 就是要等多久才执行的时间间隔
$loop = $loop > 0 ? $loop : 0;
// 如果循环的间隔为零,则停止
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_timeout($fp,1);
//设置超时
$header = "GET $path HTTP/1.1\r\n";
$header.="Host: $host\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 就是这样做的。
d6a8
我则利用文件读写特性,提供了托管在 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 去远程请求目标网站的定时任务脚本。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: