16: Demon

De Phlo Daemon is een optionele Node sidecar (phlo-daemon.js): een generieke engine die elk Phlo-doel naar een pool van persistente workers dispatcht. De kern van Phlo werkt zonder deze; de daemon versnelt de zware paden en ondersteunt WebSockets en geplande taken.

16.1: Wat het is, en waarom het optioneel is

Elke routine in je app is al aanroepbaar als een eenmalig CLI-proces (php www/app.php <target> [args...], zie het hoofdstuk Tooling). Dat is hoe async helpers, taken en WebSocket-evenementen standaard worden uitgevoerd: een nieuwe PHP-proces per aanroep, dat de app opstart, het werk doet en weer afsluit. Simpel en volledig geïsoleerd, maar een opstart per aanroep.

De daemon verwijdert die opstart. Het is een klein Node-proces dat een pool van resident PHP-werkers per app onderhoudt: elke werker start de app één keer op en beantwoordt vervolgens aanroepen via een pijp, hetzelfde idee als de HTTP-werkmode van FrankenPHP, maar voor niet-HTTP-werk. Het voegt twee dingen toe:

  1. een werknemerspool die dezelfde aanroepen uitvoert zonder een opstart per aanroep, en
  2. het langdurige hostproces dat WebSockets en geplande taken nodig hebben.

De daemon is een generieke centrale motor: de dispatch-kern weet niets over specifieke functies. De WebSocket-server (Phlo Realtime) en de planner zijn ingebouwd in de daemon; de PHP-runtime helpers bereiken het via HTTP. Het aannemen ervan is opt-in en omkeerbaar: laat het weg en alles valt terug op het eenmalige pad.

16.2: Het workerprotocol

Elke worker draait php <app.php> phlo_serve, start de app één keer op en beantwoordt vervolgens newline-gescheiden JSON op stdin, met één verzoek in behandeling per worker (concurrentie is gelijk aan de poolgrootte):

in   {"id", "target", "args"?, "stream"?}
out  {"t":"ready"}                                            // once, after boot
     {"id", "t":"line", "data"}                               // 0..N, only when stream
     {"id", "t":"done", "result"} | {"id", "t":"error", "message"}   // exactly one, terminal

target wordt verzonden met dezelfde parser als de CLI: Class::method, object.method of een blote function (zie het hoofdstuk Tooling). Per verzoek reset de worker de status (phlo('tech/reset'), sessie sluiten, GC), precies zoals de FrankenPHP worker loop, zodat taken nooit in elkaar lekken. Schrijf worker-veilige targets: geen verzoek- of gebruikersstatus in statics, commit of roll back DB-werk.

16.3: De HTTP API

De daemon bindt 127.0.0.1 standaard (alleen lokaal; beperk het aan de netwerkgrens).

POST /dispatch neemt {app, target, args?, stream?, async?}:

Sleutel Gebruikt door Betekenis
app de runtime helpers Het absolute .../app.php pad dat uitgevoerd moet worden. Een aanroeper die zijn eigen app kent, dispatcht direct; de pool is geordend op basis van het app-pad.

De ingebouwde WebSocket-server gebruikt dit eindpunt niet: het lost de Host van elke verbinding op naar een app via het register (gevuld vanuit de hosts map in config/daemon.js) en dispatcht in-process. POST /message (de broadcast brug) en GET /health completeren de API.

De respons hangt af van de modus:

Verzoek Respons
standaard {status:"ok", result}
async: true 202 {status:"ok", queued:true} (fire and forget; retourneert zodra geaccepteerd, niet zodra uitgevoerd)
stream: true een application/x-ndjson stream van {t:line,data}* dan {t:done,result} of {t:error}

GET /health retourneert het totale aantal actieve workers tegen de limiet, de per-pool statistieken geordend op app-pad, de verbonden sockets per host, en de geconfigureerde hosts:

{
	"status": "ok",
	"workers": 5,
	"cap": 7,
	"pools": { "/var/www/example/www/app.php": { "workers": 4, "busy": 1, "queued": 0 } },
	"sockets": { "app.example.com": { "tokens": 12, "sockets": 18 } },
	"registered": ["app.example.com", "dev.example.com"]
}

busy zijn de workers die momenteel een oproep afhandelen en queued zijn de oproepen die wachten op een vrije worker; workers/cap is het actuele totaal ten opzichte van de limiet (één minder dan het aantal cores). De pool groeit vanzelf naar de limiet onder belasting en haalt inactieve workers weer naar beneden, dus dit zijn observaties, geen knoppen.

16.4: Configuratie

De daemon neemt drie argumenten: een poort, de PHP-binaire en de hostmap. Er is geen poolgrootte - de pool schaalt op aanvraag.

require('./phlo-daemon.js')(3001, '/usr/bin/php-zts', {
	'dashboard.example.nl': { app: '/var/www/dashboard/www/app.php', build: true },
	'demo.example.nl': { app: '/var/www/demo/www/app.php', build: true },
})
Argument Default Betekenis
port (vereist) Poort om te binden op 127.0.0.1; sluit het af bij de proxy, expose het nooit
php (vereist) PHP-binaire die de workers draaien (/usr/bin/php-zts voor thread-veilige builds)
hosts {} De host→app kaart, { host: { app, build } }: elke app die de daemon bedient (zie hieronder)

De hostkaart is geconfigureerd. Het derde argument is de host-naar-app kaart, gedeclareerd in config/daemon.js en geladen in het register bij opstarten: elke invoer koppelt een Host aan zijn app.php pad en een build vlag. Er is geen /register eindpunt en geen registry.json; apps registreren zichzelf niet. De ingebouwde WebSocket-server gebruikt deze kaart om de Host van een verbinding te bepalen; de runtime helpers dispatchen op hun eigen app pad en hebben geen invoer nodig.

De pool schaalt zichzelf. Elke app krijgt zijn eigen pool die workers op aanvraag spawn tot een globale limiet van één minder dan het aantal cores, en oogst ze zodra ze inactief worden. Een worker wordt gerecycled na een aantal oproepen, en een vastgelopen worker wordt gedood en opnieuw opgestart. Dit is niet geconfigureerd.

Eenmalig of gepoold volgt de build vlag. Een build: true app (ontwikkeling) draait elke oproep als een nieuwe eenmalige proces, voor volledige isolatie en hot-reload. Een release app draait op de residentiële pool. Voor websockets is die vlag de per-host instelling in config/daemon.js; voor CLI dispatch sturen de runtime helpers build per oproep.

Voer het uit onder een procesmanager:

node config.js
# or
pm2 start config.js --name phlo-daemon

16.5: Runtime helpers op de pool

De async helpers behouden standaard hun eenmalige subprocess gedrag en schakelen over naar de daemon pool wanneer de app de optionele daemon constant instelt:

phlo_app(
	id: 'Api',
	host: 'api.example.com',
	daemon: 3001,
);

Met de constante ingesteld, worden deze routes via /dispatch door hun eigen app pad geleid; zonder het, starten ze een eenmalig proces precies zoals voorheen:

Helper Doet
phlo_sync('Class::method', ...$args) Voert een doel uit en wacht op de retourwaarde
phlo_async('Class::method', ...$args) Plaatst een doel in de wachtrij en vergeet het; retourneert zodra het is geaccepteerd
await($job, $job, ...) Voert veel doelen gelijktijdig uit en verzamelt hun resultaten
phlo_stream('Class::method', ...$args) Geeft de uitvoer van een doel regel voor regel terug

De winst is het grootst waar één verzoek zich verspreidt in veel aanroepen: await() over 100 ontbrekende vertalingen is 100 app opstarts op het eenmalige pad, maar 100 dispatches naar een resident pool met de daemon. Op de pool is het werk ook begrensd-parallel (gequeue tegen het aantal workers) in plaats van een onbeperkte uitbarsting van subprocessen.

$results = await(
	['translate::run', 'nl', $text],
	['translate::run', 'de', $text],
	['translate::run', 'fr', $text],
)

Omdat het aannemen van de daemon gewoon de daemon constante is, gedraagt een app zich identiek met of zonder deze; alleen de doorvoer verandert.

16.6: Planning: het cron alternatief

Elke app in de hostmap krijgt de cron-vervangende minuut-tik: de daemon voert tasks::run uit op elke bediende app eens per minuut, de eerste uitvoering een minuut na opstarten. Er is niets te configureren per taak: welke taken worden uitgevoerd en hoe vaak, wordt in de app zelf verklaard (%app->tasks, zie het hoofdstuk Taken). Een app die de tasks resource niet laadt, wordt automatisch overgeslagen na de eerste tik.

Er is hier geen planning per doel. Wat er draait en wanneer, bevindt zich in de %app->tasks van elke app (every/daily/weekly, zie het hoofdstuk Taken), zodat de planning een enkele thuisbasis heeft; de daemon levert alleen de minuut-tik. Dit vervangt de per-app cron-invoer uit het hoofdstuk Taken: met de daemon die tasks::run elke minuut uitvoert, heb je de crontab-regel helemaal niet nodig. Cron blijft de no-daemon fallback; het taakmodel, de due-checking en de on-disk status zijn in beide gevallen identiek.

16.7: Consumenten

Consumer Relationship
Phlo Realtime De ingebouwde WebSocket-server van de daemon; bezit de sockets en voert de websocket::{auth,connect,receive,close} hooks uit op de pool in-process (receive streams). Zie het hoofdstuk over WebSocket.
Runtime helpers phlo_sync / phlo_async / await / phlo_stream, opt-in via de daemon constante (X.5).
Scheduler Ingebouwd (X.6), draait tasks::run voor elke bediende app elke minuut, ter vervanging van cron.
Phlo WhatsApp Blijft een eigen service: een WhatsApp-gateway houdt een persistente telefoonsessie, wat geen taak van de worker-pool is. Het wordt gemonitord, niet opgenomen.

16.8: Wanneer het uit te voeren

Voer de daemon uit wanneer een app WebSockets nodig heeft, wanneer het taken plant zonder cron, of wanneer een hot path zich verspreidt in veel app-aanroepen per verzoek. Een kleine site die gewone HTTP bedient en nooit uitzendt, heeft het niet nodig: het eenmalige pad is voldoende, en de daemon buiten houden vermindert het aantal bewegende delen. De daemon is de optionele prestatie- en realtime-laag, nooit een afhankelijkheid van de kernverzoekverwerking.

We gebruiken essentiële cookies om deze site te laten werken. Met uw toestemming gebruiken we ook analytics om de site te verbeteren.