Comment fonctionne le PHP traditionnel (FPM)

Modele FPM

Si une application serveur PHP etait un restaurant, ce serait probablement un etablissement haut de gamme ou chaque table est servie par un serveur dedie.

Chaque nouvelle requete au serveur est traitee par une VM PHP, un processus ou un thread separe, apres quoi l’etat est detruit. C’est equivalent a un serveur qui sert une table puis est licencie ou voit sa memoire effacee.

Ce modele a un avantage : si une erreur PHP survient, une fuite de memoire, une connexion a la base de donnees oubliee – cela n’affecte pas les autres requetes. Chaque requete est isolee. Cela signifie que le developpement est plus simple, le debogage est plus simple et la tolerance aux pannes est elevee.

Ces dernieres annees, la communaute PHP a essaye d’introduire un modele avec etat, ou une seule VM PHP peut servir plusieurs requetes, en preservant l’etat entre elles. Par exemple, le projet Laravel Octane, qui utilise Swoole ou RoadRunner, obtient de meilleures performances en preservant l’etat entre les requetes. Mais c’est loin de la limite du possible.

Licencier un serveur apres chaque commande est trop couteux. Parce que les plats sont prepares lentement en cuisine, le serveur passe la majeure partie de son temps a attendre. La meme chose se produit avec PHP-FPM : la VM PHP reste inactive. Il y a plus de changements de contexte, plus de surcharge pour la creation et la destruction de processus ou de threads, et plus de consommation de ressources.

// PHP-FPM traditionnel
$user = file_get_contents('https://api/user/123');     // attente debout 300ms
$orders = $db->query('SELECT * FROM orders');          // attente debout 150ms
$balance = file_get_contents('https://api/balance');   // attente debout 200ms

// Temps passe : 650ms d'attente pure
// Le CPU est inactif. La memoire est inactive. Tout attend.

Concurrence

Modele de concurrence

Puisque la cuisine ne peut pas preparer les plats instantanement, et que le serveur a du temps d’inactivite entre les preparations, il est possible de traiter les commandes de plusieurs clients.

Ce schema peut fonctionner de maniere assez flexible : La table 1 a commande trois plats. La table 2 a commande deux plats. Le serveur apporte le premier plat a la table 1, puis le premier plat a la table 2. Ou peut-etre qu’il a reussi a apporter deux plats a la premiere table et un a la seconde. Ou l’inverse !

C’est la concurrence : le partage d’une seule ressource (CPU) entre differents fils d’execution logiques, qui sont appeles coroutines.

use function Async\spawn;
use function Async\await;

// Lancer les trois requetes "en concurrence"
$userTask = spawn(file_get_contents(...), 'https://api/user/123');
$ordersTask = spawn($db->query(...), 'SELECT * FROM orders');
$balanceTask = spawn(file_get_contents(...), 'https://api/balance');

// Pendant qu'une requete attend une reponse, on fait les autres !
$user = await($userTask);
$orders = await($ordersTask);
$balance = await($balanceTask);

// Temps passe : 300ms (le temps de la requete la plus lente)

La concurrence n’est pas le parallelisme

Il est important de comprendre la difference.

Concurrence – comme dans True Async, JavaScript, Python :

Parallelisme – c’est le multithreading (Go) :

Et ensuite ?

Maintenant vous comprenez l’essentiel. Vous pouvez approfondir :