Php - Découvrez Apmem
June 1, 2011
Dans le cadre du développement de jcray, j'ai été amené à concevoir un framework correspondant à mes goûts et besoins : flexibilité, réactivité et, surtout, cache. Son nom, Apmem vient de la contraction (ô combien présomptueuse) de Apc et de memcache.
Le système respose sur une logique MVC (model-view-controller), à la manière de Zend par exemple. Ayant pu tester ruby on rails (dont je reste éperdument amoureux ^), j'ai essayé d'adapter sa logique de model au php. Par exemple, avec 2 tables "posts" et "users" , mon framework permet de lister les posts de l'user x avec un simple :
[sourcecode language="php"]
$user = new user(x);
$posts = $user->posts
[/sourcecode]
Chaque requête mysql est envoyée à l'objet "sql_object", qui limite les résultats à l'id correspondant à l'entrée. Par exemple,
[sourcecode language="php"]
$user = new user(x);
[/sourcecode]
renverra un objet avec un id ($user->id).
Pour accèder à un autre champs pour cette entrée en base de donnée, il suffira simplement d'utiliser la propriété comme si elle existait :$user = new user(x);
echo $user->pseudo;
De l'utilisation de memcache
Gourmand, me direz vous ? En réalité, seule la première sélection l'est. En effet, chaque sql_object est stocké via memcache, et enrichi en fonction des requêtes effectuées.
Ainsi, en accédant à $user->pseudo, je "fournis" $user->pseudo à l'ensemble de tous les clients connectés ou qui se connecteront, en le stockant directement en memcache : je n'effectue qu'une fois la requête, "rendant service" à tous les autres utilisateurs.
Les requêtes complexes (de type requêtes imbriquées par exemple) sont "hashées" pour que l'id correspondant au résultat soit enregistré. Ainsi, une requête pré-hashée renverra directement l'id sans requête.
Memcache sert donc, dans ce framework, au stockage des requêtes. De plus, l'update d'un objet stocké met à jour le memcache directement :
[sourcecode language="php"]
$user = new user(x);
$user->update('pseudo', 'nouveau pseudo');
[/sourcecode]
Mettra donc à jour l'objet stocké en memcache (tout en réalisant la requête update).
De plus, un fichier de configuration permet d'ajouter rapidement un ou plusieurs nouveaux serveurs memcache au besoin. Il s'agit d'un système flexible qui permet de s'adapter rapidement à l'élargissement d'une infrastructure réseau.
En fonction de la requête, le framework exécute sur un host ou un autre. L'idée étant de prévoir un éventuel système de réplication : s'il s'agit d'une requête de type SELECT, l'objet sql_object va choisir un host parmi l'array "slave" (en fichier de configuration). S'il s'agit d'une requête update/insert/delete, sql_object va choisir un host parmi l'array "master".
De l'utilisation d'APC
Mon framework comprend un dossier appelé "core". Ce dossier contient des fichiers php détaillant des fonctions. Tous les fichiers du répertoire sont inclus via php ( avec du opendir, readdir, closedir ). Puisque le framework souhaite utiliser au moins APC, tous ces fichiers ne sont inclus qu'une fois : leur contenu est ensuite concaténé dans un fichier final, core.php, stocké dans un répertoire "cache". Puisqu'APC stocke de l'opcode, un fichier global contenant toutes les fonctions sera d'avantage apprécié qu'un grand nombre de petits fichiers. De plus, ce système de répertoire "core" permet d'insérer rapidement de nouvelles fonctions : il suffit de créer le fichier (et d'insérer le code de la fonction bien entendu), et de supprimer le fichier core.php.
Chaque vue est mise en mémoire APC, ainsi php ne "lit" le html qu'une seule fois, et "pioche" la vue directement dans la ram du serveur. Compte tenu du fait que la lecture mémoire est plus rapide que la lecture disque, cela permet d'optimiser l'accès aux vues. En revanche, cela implique de supprimer l'entrée apc en cas de modification de la vue.
Un système de "template" permet de bien séparer le html du php. La fonction "template" utilise un simple str_replace avec un array en entrée, plus économe qu'un preg_replace (et bien plus qu'un eregi_replace... d'après les benchmarks que j'ai pu consulter sur internet).
J'utilise cette fonction template de la manière suivante :
[sourcecode language="php"]
$code_html = template('fichier_template', array('un_item'=>'sa_valeur'));
[/sourcecode]
Apc va donc servir à éviter la lecture disque, tandis que memcache va éviter la requête de lecture sur la base de données.
De la sécurité ?
Chaque requête est construite via l'objet sql_object, qui permet de séparer rapidement la structure des arguments et de sécuriser ces derniers.
De plus, un fichier liste les tables existantes en base de données. Si la table utilisée dans la requête n'est pas définie dans ce fichier, la requête est annulée, et un bon die() règle l'affaire. Les risques d'injections utilisant les tables systèmes de mysql sont donc limités.
A n'importe quel moment, dans n'importe quelle vue ou template, vous pouvez facilement insérer un système de token en insérant un commentaire html de type :
[sourcecode language="html"]
[/sourcecode]
Cela insère un input de type hidden. Le coeur du framework vérifiera ensuite si le token envoyé correspond à celui mis en session. De plus, si la précédente requête était la même, elle sera ignorée (limitant ainsi les risques de "refresh sauvage").
Au final...
Inconvénients :
- Nécessite un serveur dédié pour l'utilisation de memcache et d'APC
- L'objet sql_object et sa méthode de requête n'est pas adaptée aux tables MyIsam
- La performance MySQL nécessite de conserver le cache memcache au maximum: en effaçant le memcache, le framework nécessitera beaucoup plus de requêtes qu'un autre, le temps de se "nourrir". Il en suit des schémas de montée en charge un peu particulier, où les premières requêtes sont les plus gourmandes.
- Durant la phase de développement, la modification d'une vue ou d'un template nécessite d'effacer l'item manuellement. Cela rend le développement plus lent, malgré les gains de temps avec les requêtes
Avantages:
- Résistance aux montées en charge : chaque requête effectuée par un client sera disponible et mise en cache pour tous les autres. Chaque update mettre à jour l'objet en memcache, pour tous les clients.
- Peu de lecture disque: les vues et les templates html sont mis en cache APC, reconnu pour sa rapidité de lecture. L'idée d'un cache local pour les vues et les templates permet de mettre en place des serveurs de préprod tout en bénéficiant du cache sql (via memcache)
- Structure sur le serveur très simple : un dossier pour les templates, un pour les vues, un pour les controllers. L?arborescence est lisible et explicite, pour n'importe qui.
- Un coeur adaptable. L'insertion de nouvelles fonctions dans le framework se fait en toute simplicité. Les modèles sont très accessibles, et enrichissables facilement de vos propres méthodes.
- Un coeur léger. A la base, quelques fonctions dans le core, et le système de cache soulagera le serveur.
- Facilité de load-balancing. Les fichiers de configuration permettent facilement d'insérer de nouveaux hosts master/slave pour mysql, et facilement de nouveaux serveurs memcache.
N'ayant utilisé ce framework personnel que pour jcray, je suis en train de l'essayer sur un site pour un client. Une fois les dernières retouches effectuées, je vous proposerai les sources.
A la vue de ces fonctionnalités, qu'en pensez vous ( en bien tant qu'en mal, je cherche à avancer) ?