首頁 >後端開發 >PHP問題 >php如何自動執行定時任務

php如何自動執行定時任務

爱喝马黛茶的安东尼
爱喝马黛茶的安东尼原創
2019-09-29 10:04:483942瀏覽

php如何自動執行定時任務

PHP本身是沒有定時功能的,PHP也不能多執行緒。 PHP的定時任務功能必須透過和其他工具結合才能實現,例如WordPress內建了wp-cron的功能,很厲害。本文,我們就來深入的解析幾種常見的php定時任務的思路。

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

這是什麼意思呢?實際上上面這一行由兩個部分組成,前面一部分是時間,後面一部分是操作內容。例如上面這個,

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

意思是說透過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。

相關推薦:《php入門教學

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(&#39;PRC&#39;); // 切换到中国的时间
$run_time = strtotime(&#39;+1 day&#39;); // 定时任务第一次执行的时间是明天的这个时候
$interval = 3600*12; // 每12个小时执行一次
if(!file_exists(dirname(__FILE__).&#39;/cron-run&#39;)) exit(); // 在目录下存放一个cron-run文件,如果这个文件不存在,
说明已经在执行过程中了,该任务就不能再激活,执行第二次,否则这个文件被多次访问的话,服务器就要崩溃掉了
do {
  if(!file_exists(dirname(__FILE__).&#39;/cron-switch&#39;)) 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__).&#39;/cron-run&#39;); // 这里就是通过删除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 .= &#39;?&#39;.$query;
  if($scheme == &#39;https&#39;) {
    $host = &#39;ssl://&#39;.$host;
  }
  $fp = fsockopen($host,$port,$error_code,$error_msg,1);
  if(!$fp) {
    return array(&#39;error_code&#39; => $error_code,&#39;error_msg&#39; => $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(&#39;error_code&#39; => 0);
  }
}
_sock(&#39;www.yourdomain.com/script.php&#39;);
// ...
// 继续执行其他动作
// ..

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

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

但是上面使用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(&#39;do.php&#39;);之前赋值
include(dirname(__FILE__).&#39;/do.php&#39;);

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

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

以上是php如何自動執行定時任務的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn