php實現定時任務的思路


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

Linux服務器上使用CronTab定時執行php

我們先從相對比較復雜的服務器執行php談起。服務器上安裝了php,就可以執行php文件,無論是否安裝了nginx或Apache這樣的服務器環境軟件。而Linux中,使用命令行,用CronTab來定時任務,又是絕佳的選擇,而且也是效率最高的選擇。

首先,進入命令行模式。作為服務器的linux一般都默認進入命令行模式的,當然,我們管理服務器也一般通過putty等工具遠程連接到服務器,為了方便,我們用root用戶登錄。在命令行中鍵入:

1

crontab -e

之后就會打開一個文件,並且是非編輯狀態,則是vi的編輯界面,通過敲鍵盤上的i,進入編輯模式,就可以編輯內容。這個文件中的每一行就是一個定時任務,我們新建一行,就是新建一條定時任務(當然是指這一行內按照一定的格式進行書寫)。我們現在來舉個例子,增加一行,內容如下:

1

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

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

1

00 * * * *

就是指當當前時間的分鍾數為00時,執行該定時任務。時間部分由5個時間參數組成,分別是:

1

分 時 日 月 周

第1列表示分鍾1~59 每分鍾用或者 */1表示,/n表示每n分鍾,例如*/8就是每8分鍾的意思,下面也是類推
第2列表示小時1~23(0表示0點)
第3列表示日期1~31
第4列表示月份1~12
第5列標識號星期0~6(0表示星期天)

整個句子的后面部分就是操作的具體內容。

1

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

意思就是說通過lynx訪問這個url。我們在使用中主要用到lynx、curl、wget來實現對url的遠程訪問,而如果要提高效率,直接用php去執行本地php文件是最佳選擇,例如:

1

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文件,然后用文本編輯器打開它(記事本都可以),在里面寫上這樣的內容:

1

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秒鍾。實際上,這種長時間等待的行為是比較消耗資源的,不能大量使用。

那么定時任務到底怎么實現呢?使用下面的代碼即可實現:

1

2

3

4

5

6

7

8

9

<?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,那么我們在編程中,可以這樣:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// ...// 正常的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 .= '?'.$queryif($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');// ...// 繼續執行其他動作// ..

把這段代碼加入到某個定時任務提交結果程序中,在設置好時間后,提交,然后執行上面這個代碼,就可以激活該定時任務,而且對於提交的這個用戶而言,沒有任何頁面上的堵塞感。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM