Task scheduling with PHP
- 09-11-2009 @ 13:53
- 0 reacties
This post is bilingual, for English scroll down.
In het KMT wordt er gewerkt met vertraagde afhandeling van informatie. Dit wordt ook batchprocessing genoemd. Vaak zijn dit zaken die intern allerlei dingen controleren, rechtzetten of juist opruimen. Ook kan het bijvoorbeeld zijn dat er aan het einde van de dag een samenvatting van werkzaamheden naar gebruikers moet worden gestuurd. Omdat dit niet op aanvraag van een gebruiker gebeurt maar direct vanuit het systeem is het noodzakelijk hier een automatisch startend proces voor te vinden. In feite betekent het dat een taak ingepland en automatisch gestart moet worden. Hiervoor heeft Kingsquare een drietal methodes gebruikt:
- Linux anacron daemon
- PHP Crondaemon
- Taskspooler
Die eerste optie is natuurlijk het meest voor de hand liggende: gebruik een periodiek systeem waar iedere minuut een bepaald script opgestart kan worden. Toch bleek dit niet voldoende: er zijn namelijk processen die langer dan een minuut duren en daardoor bleek dat er een situatie kon ontstaan waarbij een bepaalde taak werd 'overgeslagen' doordat een andere taak te lang erover deed. Hiervoor moest een oplossing komen: de PHPCronDaemon. Een systeem proces geschreven in PHP die een takenlijst iedere ronde automatisch probeert af te handelen. De ideeën hiervoor lagen al een tijdje op de plank, toch bleek dat PHP uiteindelijk niet de meest geschikte motor was voor een continu draaiend proces. Het werkt, maar het werkt niet zo mooi :)
Dan de oplossing: de taskspooler. Deze tool draait continu. Daarnaast maken we gebruik van een cron.php die kijkt of er nieuwe taken zijn en deze in de wachtrij van 'ts' plaatst. De taskspooler loopt vervolgens deze wachtrij af en draait alle taken automatisch via worker.php af. Doordat we eenvoudig taken willen kunnen managen is dit alles geïntegreerd met onze database architectuur. Iedere omgeving kan dus volgens een crondefinitie een taak aanmaken. Deze wordt dan automatisch opgepakt, uitgevoerd en vervolgens worden de resultaten weer terug naar de database gestuurd. Op die manier is ook later nog terug te zoeken wat welke taak wanneer heeft gedaan.
Inner workings
in de cron is opgenomen:
# task spooler cron!
* * * * * /path/to/cron/cron.php
#!/usr/bin/php4
<?php
require dirname(__FILE__).'/../inc/cronBootstrap.php';
foreach(getObjects('Crontab') as $crontab) {
$cronParser = new CronParser($crontab->getCronDefinition());
if ($cronParser->getLastRanUnix() > $crontab->getLastActualTimestamp()) { // should this definition run again?
$crontab->setLastActualTimestamp(time());
$crontab->update();
$crontab->createCronJob(); // create the job
}
}
// init the right environment variables
//exec('export TS_ONFINISH='.__DIR__.'/inc/workerFinish.php');
// spool unspooled jobs
$sqlArray = array();
$sqlArray['WHERE'][] = '`startTimestamp` <= NOW()';
$sqlArray['WHERE'][] = '`tsId` = -1';
foreach(getObjects('CronJob', false, $sqlArray) as $cronJob) {
$tsId = exec('/usr/local/bin/ts '.__DIR__.'/inc/worker.php '.$cronJob->getId());
$cronJob->setTsId($tsId);
$cronJob->update();
}
Dit lijkt misschien ingewikkeld, maar er staat zoiets als: haal de crontabs (taak definities) op, verwerk deze en maak eventueel nieuwe taken aan. Vervolgens worden alle taken die nog niet opgenomen zijn in Taskspooler (tsId = -1) aan Taskspooler overhandigd en wordt het Id opgeslagen. Of tewel: de takenlijst wordt nu automatisch uitgebreid met nieuwe taken.
Dan het verwerken van de taak: de worker.php
<?php
require dirname(__FILE__).'/../../inc/cronBootstrap.php';
$cronJob = new CronJob((!empty($_SERVER['argv'][1]))?(int)$_SERVER['argv'][1]: false);
if (is_a($cronJob, 'CronJob') && $cronJob->getId()) {
echo 'CronJob started @ '.date('d-m-Y H:i:s').PHP_EOL;
// execute the code
$cronJob->execute();
print PHP_EOL.'CronJob finished @ '.date('d-m-Y H:i:s').PHP_EOL;
} else {
alertKingsquare('Ongeldige worker aangesproken', print_r($_SERVER['argv'], true));
}
Ook hier weer een vrij zelf verklarend stuk code die niets anders doet dan de cronjob (een taak definitie) uit de database ophalen en uitvoeren. Eigenlijk dus vrij eenvoudig. Indien dit vervolgens niet lukt om een of andere reden horen we dit graag en vandaar dus de alertKingsquare.
Als laatste staat de taskspooler zodanig ingesteld dat na het uitvoeren van een taak automatisch workerFinish.php wordt aangeroepen met het taskID nummer. (zodat ook hier weer wat mee gedaan kan worden) Deze workerFinish ziet er vervolgens zo uit:
<?php
require dirname(__FILE__).'/../../inc/cronBootstrap.php';
//jobid, error, outfile, command
$tsId = ((!empty($_SERVER['argv'][1]))?$_SERVER['argv'][1]:'');
$errorlevel = ((!empty($_SERVER['argv'][2]))?$_SERVER['argv'][2]:'');
$outputFile = ((!empty($_SERVER['argv'][3]))?$_SERVER['argv'][3]:'');
$sql = '
UPDATE `cronJob`
SET
`results` = '.$db->Quote(file_get_contents($outputFile)).',
`endTimestamp` = NOW(),
`errorLevel` = '.$db->Quote($errorlevel).'
WHERE `tsId` = '.$db->Quote((int) $tsId). ' AND `endTimestamp` = 0';
if (!$db->Execute($sql) || $errorlevel != 0) {
$output = file_get_contents($outputFile);
alertKingsquare('error on job!', $db->ErrorMsg().PHP_EOL.print_r($_SERVER['argv'],true).PHP_EOL.substr($output,-500,000));
}
Wederom niet heel spannend: pak de output en zet dit terug in de database. Daarbij willen we het wederom weten als er iets niet in de haak blijkt te zijn.
Zo is het dus mogelijk om ondanks het feit dat een taak misschien wel uren duurt en daardoor andere taken in de wacht zet, deze niet te vergeten en gewoon af te werken zodra hiervoor tijd is. Op deze manier kunnen nieuwsbrieven op een zelf vast te stellen moment worden verstuurd, video's automatisch worden geconverteerd op het moment dat ze worden opgevraagd, geschiedenis automatisch worden opgeschoond en dataintegriteitscontroles worden uitgevoerd.
--
English