Aujourd’hui, les grandes entreprises et administrations publiques hésitent entre continuer à utiliser des logiciels propriétaires ou basculer vers les Logiciels Libres. Pourtant, la plupart des logiciels libres sont capables de bien traiter les données issues des logiciels propriétaire, et parfois avec une meilleur compatibilité.
C’est alors la barrière de la prise en main qui fait peur, et pourtant...
Les logiciels libres
L’aspect « Logiciel Libre » permet une évolution rapide et une plus grande participation des utilisateurs. Les aides et tutoriels foisonnent sur Internet ou sont directement inclus dans le logiciel lui-même.
Enfin, les concepteurs sont plus proches des utilisateurs, ce qui rend les logiciels libres plus agréable à utiliser et conviviaux.
Grâce à la disponibilité des logiciels libres, vous trouverez facilement des services de support techniques et la licence n’est plus un frein à l’utilisation de ces logiciels par votre personnel.
Notre support technique concerne essentiellement les logiciels libres, que ce soit sous forme de services ponctuels ou de tutoriels.
- Janvier 2011 -
Bonjour à tous et
Bonne Année à ceux qui ont raté la première salve !Je vous parlais
ici de comment aborder calmement
NGinx, cet outil russe jouant serveurs web et reverse proxy.Je vous propose donc cette fois-ci quelques idées qui pourront améliorer d'un peu les
performances de votre service.
Factorisation, segmentation
Avant de parler fonctionnalité, mettons l'accent sur les grands fondamentaux : un code clair et surtout bien organisé.Bien sûr, il ne s'agit pas de faire la leçon en terme de lisibilité ou autre perche tendue à un utilisateur inexpérimenté, mais bien de
réorganisation d'instructions, afin que NGinx ne perde pas son temps à évaluer et réévaluer une expression qui n'a pas forcément à l'être -du moins, pas autant de fois.
Factorisation
Un exemple est très frappant, qui est donné par la Communauté :
http {
index index.php index.htm index.html;
server {
server_name www.domain.com;
location / {
index index.php index.htm index.html;
[...]
}
}
server {
server_name domain.com;
location / {
index index.php index.htm index.html;
[...]
}
location /foo {
index index.php;
[...]
}
}
}
On constate la répétition inutile -mais ici, elle coûte pas en performances, c'est déjà ça- des fichiers à rechercher, par ordre de préférence.Le copier-coller est facile, mais pas très élégant, surtout lorsque l'on sait que NGinx utilise le fichier de conf -donc les blocs et sous-blocs- comme une
hiérarchie. D'autant plus que si l'on définie un nouveau serveur, mais qu'on oublie notre joli paste, on peut s'attendre à ce que NGinx botte en touche.Pourquoi dans ce cas, ne pas profiter de l'héritage ?
http {
index index.php index.htm index.html;
server {
server_name www.domain.com;
location / {
[...]
}
}
server {
server_name domain.com;
location / {
[...]
}
location /foo {
[...]
}
}
}
Comme je le souligne plus haut, cet exemple n'illustre pas une perte de performance sur l'évaluation d'une instruction.
En revanche, prenons dans le cas inverse : que se passerait-il si justement on stipulait très tôt un traitement déclenché par une regex ?
Segmentation
N'oublions pas que NGinx fonctionne non pas sur la base de session mais bien de requête. Une regex placée un peu trop haut impliquerait son évaluation pour toutes les requêtes devant passer par le bloc, et donc impacterait plus significativement les performances.Il en va de même pour n'importe quel type de test, y compris le fameux If.Exemple :
server {
server_name domain.com *.domain.com;
if ($host ~* ^www\\.(.+)) {
set $raw_domain $1;
rewrite ^/(.*)$ $raw_domain/$1 permanent;
[...]
}
}
Ici, à chaque requête, on doit analyser le
Host Header pour voir s'il colle à notre if regexpé. Autant dire qu'on fait travailler le serveur pour pas grand' chose...A ce moment, et comme on opère tout de même un
rewrite si le if est validé, tant qu'à faire, autant fractionner au plus près des besoins !En mieux, on tombe sur un truc du genre :
server {
server_name www.domain.com;
rewrite ^ $scheme://domain.com$request_uri? permanent;
}
server {
server_name domain.com;
[...]
}
Le premier bloc s'occupera donc d'opérer le
rewrite pour toute requête s'adressant à
www.domain.com, et uniquement celles-là, et les renverra vers
domain.com. Une fois renvoyées, elles ne passeront plus systématiquement par l'évaluation du
Host Header. Ca, c'est fait.Même principe pour ceux qui poussent tout sur
PHP. Plutôt que d'envoyer tout et absolument tout au proxy, autant segmenter pour coller au plus près de ce que l'on doit fournir à PHP, quitte à passer par un
try_file.
server {
server_name _;
root /var/www/site;
location / {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/tmp/phpcgi.socket;
}
}
En mieux :
server {
server_name _;
root /var/www/site;
location / {
try_files $uri $uri/ @proxy;
}
location @proxy {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/tmp/phpcgi.socket;
}
}
Le
try_file permet de tester d'abord la présence de la donnée en locale, et de la servir. Sinon, on passe par une location factice appelée
@proxy qui s'appliquera à pousser tout ce petit monde à PHP.Bref vous l'avez compris, la décomposition des éléments joue un rôle très appréciable ! Non seulement votre configuration est claire, mais en plus ça a l'avantage d'améliorer de manière drastique la façon dont sont servies les données et donc les performances de la bête.
Compression
Cette fois-ci, penchons-nous non pas sur le temps de process d'une requête mais sur le goulot d'étranglement classique : la bande passante.En effet, la richesse et la forte sollicitation d'un site peut facilement peser sur un serveur, surtout si celui-ci est tout seul et donc ne dispose pas de moyen de gérer la charge.NGinx dispose d'un ensemble de
modules qui prennent en charge la compression et la réutilisation des éléments déjà compressés qui seront poussés vers le client. Il s'agit notamment des modules suivants :
- HttpGzipModule, module mettant à disposition une fonctionnalité de compression à la volée,
- HttpGzipStaticModule, le module qui permet de rechercher et d'utiliser une version compressée d'un élément, plutôt que de recompresser une donnée déjà traitée. Par contre, pour disposer de ce module, il faut compiler NGinx avec le mot magique : ./configure --with-http_gzip_static_module
Concrètement, à la première requête, le délai de réponse souffrira un peu de la compression de l'élément, mais grâce à une gestion plus intelligente, l'opération n'est faite que si nécessaire.Il suffit ensuite de mettre NGinx au courant, au niveau du contexte (du super-bloc) http :
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_vary on;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml
application/xml+rss text/javascript;
D'ailleurs, on remarque avec un intérêt le paramètre
gzip_proxied : il s'agit de
compresser la réponse faite par le proxy.
Plusieurs paramétrages sont possibles, et en plus cela permet d'introduire une autre fonctionnalité permettant de soulager votre serveur : le cache !
Cache
Par location
Il arrive que certaines -beaucoup parfois- des données qui sont sollicitées sont ce qu'on appelle des données statiques : elles ne changent jamais.En jouant sur la gestion de ce type de source, on peut donc facilement améliorer les performances de notre service. D'abord en isolant bien proprement le
bloc associé, afin de ne pas rentrer dans des moulinets gourmands alors que ces données n'ont besoin d'aucun traitement.Mais surtout il y a le cache. Si elles ne changent que très rarement, pas besoin d'aller systématiquement les chercher, autant les mettre en cache et forcer une
expiration relativement raisonnable.Bon, 10 ans, c'est quand même un peu beaucoup...
location ~ ^/(images|javascripts|stylesheets)/ {
expires 10y;
}
En configuration Reverse Proxy
Possible également d'utiliser le cache dans une configuration de
reverse proxy, comme en témoigne cet exemple sorti du wiki :
http {
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m
inactive=24h max_size=1g;
server {
location / {
proxy_pass http://proxy.domain.com;
proxy_set_header Host $host;
proxy_cache STATIC;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating
http_500 http_502 http_503 http_504;
}
}
}
Pour expliquer un peu :
- le proxy_cache_path stipule les paramètres de stockage et d'utilisation du cache : chemin(/data/nginx/cache), niveau de sous-répertoire à mettre en cache(1:2), le nom et la taille de la zone (STATIC:10m), le temps durant lequel le cache est gardé et à partir duquel il est flushé (24h)...
- proxy_pass explicite le serveur qui va jouer le proxy. Sa définition est à votre convenance : IP, IP:port, FQDN, socket...
- proxy_cache reprend la zone définie par proxy_cache_path, quant à proxy_cache_valid, il paramètre la durée du cache en fonction des réponses retournées : ici 1 jour pour un code 200 renvoyé. Il est d'ailleurs possible d'avoir de multiples instructions proxy_cache_valid, pour chaque code retour intéressant, afin de pouvoir stipuler les durées selon les besoins.
- proxy_cache_use_stale enfin spécifie quand servir les caches plutôt que de requêter les éléments, mais aussi de faire en sorte (option updating) que si plusieurs requêtes demandent un update de l'élement mis en cache, seule une traitera cet update tandis que les autres continueront à se servir du cache, le temps que l'update soit fait.
Au final, on verra que le cache a écrit ses données dans un fichier nommé et référencé grâce au
hash MD5 de l'URL qui est passée au proxy.Pour ceux qui veulent approfondir, c'est
par ici !Évidemment, NGinx n'est pas le seul à savoir gérer les caches, et certaines solutions très pertinentes peuvent très bien se marier avec notre produit russe. Citons par exemple
MemCached, dont le support est
intégré de manière standard.L'exemple pour la forme est celui du wiki :
server {
location / {
set $memcached_key $uri;
memcached_pass name:11211;
default_type text/html;
error_page 404 @fallback;
}
location @fallback {
proxy_pass backend;
}
}
NGinx intègre également un module
memc, qui est une version étendue de memcached.
Memc offre des options de gestion et de manipulation de commande
Memcached.La configuration est nettement moins sympa, mais reste claire tout de même :
# GET /bar?cmd=get&key=cat
# GET /bar?cmd=set&key=dog&val=animal&flags=1234&exptime=2
# GET /bar?cmd=delete&key=dog
# GET /bar?cmd=flush_all
location /bar {
set $memc_cmd $arg_cmd;
set $memc_key $arg_key;
set $memc_value $arg_val;
set $memc_flags $arg_flags; # defaults to 0
set $memc_exptime $arg_exptime; # defaults to 0
memc_cmds_allowed get set add delete flush_all;
memc_pass 127.0.0.1:11211;
}
Je vous renvoie à la page du
wiki pour plus explicite.
Remaniement des requêtes
Server name vs Host Header
Dans le cas d'une configuration simple, c'est-à-dire que tout les serveurs écoutent sur la même adresse et sur le même port, de configuration identique, il est possible de
bypasser l'évaluation du
Host Header des requêtes soumises et de les rediriger automatiquement sur le premier
server_name donné.C'est le rôle de l'instruction
optimize_server_names [on|off]. Par défaut, ce paramètre est à on. Cela permet notamment d'éviter l'analyse systématique des
Host Header de chacune des requêtes arrivant au serveur.
Création de module
Enfin, si tout cela ne couvre pas vos besoins et que vous êtes développeur dans l'âme, vous pouvez très bien vous pencher sur le développement de module.Jetez un coup d'oeil
par ici si ça vous tente :)
Ouverture
En bref, NGinx est vraiment très riche, et je suis sûrement encore loin du compte ! En revanche, si vous avez pu expérimenter d'autres optimisations, n'hésitez pas à faire un petit retour de vos expériences, bonnes ou mauvaises !Sur ce, Enjoy ![Edit] : N'hésitez pas à jeter un coup d'œil sur
le blog de NicoLargo, il y a pas mal de choses qui valent le détour :)

Original post of K-Tux.
Votez pour ce billet sur Planet Libre.