PHP-Schnipsel: Hintergrundjobs durch Seitenaufrufe auslösen lassen

Sonntag, 6. März, 2011

Wenn man bei seinem Provider keinen Cronjob einrichten kann, aber genügend Besucher hat, kann man ggf. Hintergrundjobs während der Seitenaufrufe auslösen. Man muss dafür sorgen, dass nicht jeder einzelne Seitenaufruf die Verarbeitung triggert, sondern es soll max alle n Sekunden geschehen.

In PHP definiere ich ein Array mit der Konfiguration und ein Arbeitsverzeichnis. Im Arbeitsverzeichnis wird bei Ausführung eines Jobs eine Datei zum Merken des letzten Ausführungsdatums getoucht (jaja, ein schönes deutsches Wort). Soll derselbe Job erneut ausgeführt werden, wird das Alter der Datei im Arbeitsverzeichnis geprüft. Wenn das Alter Älter als mein definiertes Limit ist, dann wird der Job erneut ausgeführt - ansonsten nicht.

Klingt einfach … - ist es auch ;-)

Voraussetzungen, damit das nachfolgende Beispiel funktioniert:

  • wget muss am Webserver vorhanden sein (alternativ liessen sich auch curl oder lynx verwenden)
  • PHP-Funktion exec muss zugelassen sein
  • getestet wurde es nur mit einem Unix-System als Webserver (für Windows s. Anmerkungen unten)


Konfiguration: inc_config.php (Jobs und Arbeitsverzeichnis):

<?php
/* 
 * CONFIG
 * jobs to trigger
 */

$aJobs=array(
  array(
      'title'=>'Statistik aktualisieren',
      'timer'=>600, // in Sekunden -> 600 = 10 min
      'url'=>'http://www.example.com/statistik_aktualisieren.php'
  ),
  array(
      'title'=>'Job 2',
      'timer'=>300,
      'url'=>'http://www.example.com/job_2.php'
  ),
  (...)
);

$workdir=$_SERVER['DOCUMENT_ROOT'].'/~trigger/';

?>

Meine Jobs sind URLs - diese werden mit der PHP-Funktion exec mit Hilfe von wget als Hintergrundjob übergeben.

$sCmd='/bin/wget -O /dev/null '.$a['url'].' >/dev/null 2>&1  &';
$exec = exec($sCmd);

Das “&” am Ende ist wichtig, damit nicht auf das Ende der Ausführung des aufgerufenen Kommandos gewartet wird. Sonst entstünden für den Webseitenbesucher lange Wartezeiten.

Unter Windows funktioniert diese Art Aufruf nicht. Aber mit dem Kommando

start %COMSPEC% /C [Programm-Aufruf]

lässt sich gewiss eine ebenfalls funktionierende Variante finden.

Das komplette PHP-Skript:
Der nachfolgende PHP-Schnipsel lädt die Konfiuration und prüft in einer foreach Schleife jeden einzelnen Job durch, ob dieser beim aktuellen Aufruf auszuführen ist. Aktionen stehen in der Variable $sOut, die als HTML-Kommentar zuunterst ausgegeben wird.

<?php
$sOut="START ".date("Y-m-d H:i:s")."<br>";
include_once 'inc_config.php';
if(!file_exists($workdir)) mkdir($workdir);

foreach ($aJobs as $a){
    $tf=$a['url'];
    $tf=preg_replace('/W/', '', $tf);
    $tf=$workdir.$tf;
    $sOut.='<strong>'.$a['title'].'</strong><br>';
    $sOut.="timer: ".$a['timer']."s<br>";

    $bExec=true;
    if(file_exists($tf)){
        $bExec=false;
        $aStat=stat($tf);
        $iAge=time()-$aStat[9];
        if($iAge>$a['timer']) $bExec=true;
        $sOut.="last exec: ".$iAge." s ago<br>";
    } else{
        $sOut.="last exec: never (touchfile was not found)<br>";
    }

    if($bExec){
        $sOut.="executing ".$a['url']."<br>";
        touch($tf);
        $sCmd='/bin/wget -O /dev/null '.$a['url'].' >/dev/null 2>&1  &';
        $sOut.=$sCmd."<br>";
        $exec = exec($sCmd);
    } else {
        $sOut.="SKIP ".$a['url']."<br>";
    }
    $sOut.='<hr size=1>';
}
$sOut.="done (".date("Y-m-d H:i:s").")";

echo "<!-- $sOut -->";
?>