A háttéreljárások hátulütőiről
A minap belefutottam a gyakorlatban egy érdekes problémába a PHP-ban írt háttérben futó folyamatai kapcsán. Ennek kapcsán úgy döntöttem, összeszedem azokat a baklövéseket, amiket egy fejlesztő elkövethet aszinkron folyamatok írásakor.
Élek a gyanúperrel, hogy a fejlesztői társadalom nagy százaléka – tisztelet természetesen a kivételnek – a háttérfolyamatait úgy valósítaná meg, hogy crontabból elindítana egy PHP fájlt, ami aztán mindenféle varázslatos úton-módon megcsinálná a megcsinálandót a saját logikája szerint. Lássuk a buktatókat:
- Egymásra futás: amennyiben a fejlesztő nem ügyel arra, hogy egyszerre több ilyen PHP script is futhasson, könnyen előfordulhat az, hogy egyre növekvő számú szál kezdi el ugyanazt feldolgozni, amíg végül az oprendszer meg nem adja magát. Ez azért alattomos, mert a fejlesztői környezetben jóval egy perc alatt lefutnak a feladatok, azonban ahogy kezd nőni az adatmennyiség és végrehajtandó feladatok száma, úgy kerül a végrehajtás ideje egyre közelebb a halálos egy perchez, ami a crontab minimális felbontása és ahol el tud kezdődni az egymásra futás.
- Éhezés: egy PHP script átlagos módszerekkel egy szálon fut. Ez azt is jelenti, hogy alapvetően a feladatok egymás után hajtódnak végre, tehát ha nem alkalmazunk valamiféle prioritást, előfordulhat az a kellemetlen eset, hogy egy viszonylag rövid és gyors futást igénylő folyamatot feltartóztat egy hosszabb lefutású valami.
- Monitorozás: mint fejlesztők, hajlamosak vagyunk abból kiindulni, hogy az alattunk működő rendszer bármilyen körülmények között teszi a dolgát, nincsenek tranziens vagy permanens hibák. Mivel a háttérfolyamatainkra azonban nem vigyáz semmilyen webszerver, nincs, ami helyrehozza azt, ami lerohadt, érdemes egy kicsit foglalkozni a monitorozással is.
A kérdés adott, milyen megoldásokkal lehet ezt kikerülni?
Egymásra futás
Az egymásra futás megoldása viszonylag egyszerű, ki kell olvasni a saját process ID-nkat a posix_getpid()
függvénnyel, majd le kell írni egy fájlba. (Ha a fájlban már van valami, akkor természetszerűleg nem szabad így tenni.) A dolog egyetlen hátulütője az, hogy ha a PHP egy fatal errorral eltávozik az örök bitmezőkre, nincs ki eltakarítsa a pidfájlt, a háttérfolyamatok leállnak. Pontosan ezért érdemes a PHP futást egy shell scriptbe burkolni, ami ezt megoldja:
#!/bin/bash
PIDFILE="/var/www/example.com/mybackgroundprocess.pid"
if [ -f $PIDFILE ]; then
exit 1
fi
echo $$ >$PIDFILE
/usr/bin/php /var/www/example.com/cron.php
rm $PIDFILE
Ez nem is volt olyan bonyolult. Természetesen ettől még lepusztulhat az egész, de ez a monitorozás témakörébe tartozik.
Éhezés
Az éhezés egy izgalmas témakör, mert a tesztkörnyezetben ritkán jön elő. Tegyük fel, hogy van három fajta feladatunk, amit prioritási sorrendben futtatunk, egyet egyszerre. A teszt környezetben semmi gond, mindegyiknek viszonylag ritkán akad teendője, ezért szépen sorban lefutnak, mindegyik három percenként. Az éles rendszerben viszont kezdenek jönni a gondok, a futás lassú lesz, mivel mondjuk az elsőnek annyira besűrűsödtek a dolgai, hogy a többi a szó legszorosabb értelmében éhezik, egyszerűen nem jut el odáig, hogy fusson.
Fejlesztőként szeretünk tökéletes kontrollt alkalmazni a rendszer felett, ezért sokan döntenek úgy, hogy PHP-ból vezérlik a feladatok időzítését. A konkrét folyamatok vannak, akkor viszont érdemesebb lehet a crontabbal elvégeztetni az időzítést és minden háttér folyamatnak különálló cron bejegyzést és indítófájlt készíteni.
Ezzel ellentétben, ha olyan feladatok vannak, amik előre ki nem számítható időközökben jönnek létre egy feladat sorban, érdemes feladatonként kezelni (megjelölni és ellenőrizni azt, hogy egy feladat futása folyamatban van-e) és a cronjobokat hagyni egymásra futni. Ha felsűrűsödnek a teendők, az egymásra futó folyamatok révén egyszerre több szálon hajtódnak végre. A feladat szintű zárolás pedig segít abban, hogy ne legyen géprohasztó az egész. Természetesen ez egy meglehetősen nyers megoldás, sokkal kifinomultabb eszközök állnak rendelkezésre, ha lehetőségünk van saját daemont futtatni például Gearmannel vagy más messageing vagy task queue rendszerekkel.
Monitorozás
Ha saját virtuális vagy fizikai szerver tulajdonosok vagyunk, minden bizonnyal beüzemeltünk már valamiféle monitorozást (ha nem, akkor ejnyebejnye). Linux esetén jó esély van rá, hogy ez a szoftver a Nagios lesz, ami azért jó, mert baromi egyszerű hozzá plugint írni.
<?php
const NAGIOS_OK=0;
const NAGIOS_WARNING=1;
const NAGIOS_CRITICAL=2;
const NAGIOS_UNKNOWN=3;
$status = NAGIOS_UNKNOWN;
// Service check itt
exit($status);
Innentől már csak annyi a feladatunk, hogy kitaláljuk, mire milyen eredményt szeretnénk kapni. Ezek után konfiguráltassuk be a rendszergazdával a service checket, ami aztán akár még SMS-t is küldhet, ha probléma van.
További olvasmányok
Ha szeretnénk egy kicsit jobban elmélyedni a témában és megérteni az időzítő algoritmusokat, érdemes elolvasni Andrew S. Tannenbaum és Andrew S. Woodhull Operációs Rendszerek (tervezés és implemencáció) című könyvéből a megfelelő részeket. A könyv egyébként igen-igen hasznos bármely fejlesztő számára, úgyhogy aki nem fél egy kis elméleti alapozást szerezni, annak mindenképpen ajánlanám elolvasásra.
Amennyiben crontab helyett a daemonok kerülnek elő a megvalósításnál, érdemes elolvasni a Weblaboron is megjelent Nagy terhelésű rendszerek fejlesztése 2 cikket (shameless self promo).
■
Operációs rendszerek
Egymásra futás
Inkább így:
ertesitest kuldhetnel ha ott
Tyrael
Soha nem értettem az ilyen
- egyim.php elindul
- a PID-jét, mondjuk 12345, beírja az enyim.pid állományba
- enyim.php futása idő előtt megszakad, az enyim.pid megmarad
- egyéb folyamatok indulnak, köztük egy XMMS, a PID-je mondjuk 12345
- enyim.php megint elindul
- megtalálja az elődje PID-jét az enyim.pid állományban
- ha az ottmaradt PID-el fut folyamat, méghozzá ugyanaz a futtatható mint az ő és a felhasználó is stimmel, akkor kilép
A vastagított feltételt még sehol sem láttam. (Igaz PHP esetén nem ilyen sima ügy.) Vagy csak én tulajdonítok ennek fontosságot?igazabol ahogy te is irod,
vagy joval lassabban fut a script, mint ahogy mi szamitunk ra, vagy pedig abortalva lett a futasa.
ilyenkor a legbiztosabb a manualis ellenorzes, kozbeavatkozas.
ha robosztusabb rendszer kell akkor erdemesebb lehet elgondolkozni valami daemonizalt alkalmazason.
a monitor alkalmazasunk(nagios) figyelheti hogy fut-e a daemon alkalmazasunk, a daemon alkalmazasunk meg kiexecelheti/kiforkolhatja magabol a workereket, akiknek a futasat tudja figyelni, kontrolalni, szukseg eseten kiloheti, ujraindithatja, vagy leallithatja oket.
Tyrael
Daemonizálás nyet
Ajánlom figyelmetekbe a supervisor python kliens-szerver alkalmazást, amellyel előtérben futó alkalmazásokat lehet menedzselni, képes figyelni arra, hogyha leállt egy felügyelt folyamat, akkor az újra elinduljon, illetve többszöri sikertelen újraindítás után képes riasztást küldeni.
A kliens részével lehetőségünk van a supervisord által felügyelt processek kimeneteire ránézni, illetve leállítani-újraindítani egyes folyamatokat. Rendelkezik webes frontenddel is, bár az a része messze nem tökéletes.
Ezen kívül ha rootként fut a supervisord, akkor lehetőségünk van arra, hogy a különböző folyamatokat nem privilégizált user nevében futtassuk.
Jelen pillanatban most egy migráció kapcsán 4 szerveren több mint 130 processt felügyeltetek ezzel az eszközzel, emellett 4 gearman tölti be a messagebus szerepét.
Lock
A monitorozás meg egy másik kérdés, azt úgysem fogod megúszni és kezelni kell mindenféle helyzetet. Sajnos a nagyobb hiányosságok inkább ezen a ponton találhatóak. Sajnos kevés PHP fejlesztő kezéből kikerülő jól monitorozható szoftvert láttam eddig életemben. Olyat, ami kiköhögött magából egy OK stringet egy Móricka-teszt után persze rengeteget, az ilyenkor kötelező önérzettel együtt. Hogy metrikákat mondjon a saját állapotáról, Muninnal grafikont rajzoljon belőle az ember csak álmodni merek, pedig mindenkinek a saját jól felfogott érdeke, hogy ilyet írjon.