Dans la vie de chaque projet, le moment vient où le serveur cesse de répondre aux exigences SLA et commence littéralement à s'étouffer sur la quantité de trafic entrant. Après cela, le long processus de recherche de goulots d'étranglement, de requêtes lourdes, d'index mal créés, de données non mises en cache ou vice versa, de données trop souvent mises à jour dans le cache et d'autres côtés sombres du projet, commence.
Mais que faire lorsque votre code est «parfait», toutes les demandes lourdes sont placées en arrière-plan, tout ce qui était possible a été mis en cache et le serveur n'atteint toujours pas les indicateurs SLA dont nous avons besoin? Si possible, vous pouvez bien sûr acheter de nouvelles voitures, répartir une partie du trafic et oublier le problème pendant un certain temps.
Mais si vous avez le sentiment que votre serveur est capable de plus, ou s'il existe un paramètre magique qui accélère le site de 100 fois, vous pouvez rappeler la fonction nginx intégrée qui vous permet de mettre en cache les réponses du backend. Voyons ce que c'est et comment cela peut aider à augmenter le nombre de demandes traitées par le serveur.
Qu'est-ce que le cache Nginx et comment fonctionne-t-il?
Le cache Nginx peut réduire considérablement le nombre de demandes pour le backend. Ceci est réalisé en enregistrant la réponse HTTP pendant un certain temps et en accédant à nouveau à la ressource, en la renvoyant du cache sans mandataire de la demande pour le backend. La mise en cache, même pour une courte période, augmentera considérablement le nombre de requêtes traitées par le serveur.
Avant de procéder à la configuration de nginx, vous devez vous assurer qu'il est construit avec le module «ngx_http_proxy_module», car nous allons le configurer à l'aide de ce module.
Pour plus de commodité, vous pouvez placer la configuration dans un fichier séparé, par exemple, «/etc/nginx/conf.d/cache.conf». Examinons la directive proxy_cache_path, qui vous permet de configurer les paramètres de stockage du cache.
proxy_cache_path /var/lib/nginx/proxy_cache levels=1:2 keys_zone=proxy_cache:15m max_size=1G;
«/ Var / lib / nginx / proxy_cache» spécifie le chemin de stockage du cache sur le serveur. C'est dans ce répertoire que nginx enregistrera les fichiers mêmes avec la réponse du backend. En même temps, nginx ne créera pas indépendamment un répertoire pour le cache, vous devez vous en occuper vous-même.
"Niveaux = 1: 2" - définit le niveau d'imbrication des répertoires avec un cache. Les niveaux d'imbrication sont indiqués par «:», dans ce cas 2 répertoires seront créés, au total 3 niveaux d'imbrication sont autorisés. Pour chaque niveau d'imbrication, des valeurs de 1 à 2 sont disponibles, indiquant comment créer le nom du répertoire.
Le point important est que le nom du répertoire n'est pas choisi au hasard, mais est créé en fonction du nom du fichier. Le nom du fichier, à son tour, est le résultat de la fonction md5 de la clé de cache; nous examinerons la clé de cache un peu plus tard.
Voyons en pratique comment le chemin d'accès au fichier cache est construit:
/var/lib/nginx/proxy_cache/2/49/07edcfe6974569ab4da6634ad4e5d492
Le paramètre "Keys_zone = proxy_cache: 15m" définit le nom de la zone dans la mémoire partagée, où toutes les clés actives et les informations les concernant sont stockées. Grâce à «:» indique la taille de la mémoire allouée en Mo. Selon nginx, 1 Mo suffit pour stocker 8 000 clés.
"Max_size = 1G" définit la taille maximale du cache pour toutes les pages au-dessus desquelles nginx se chargera de supprimer les données moins nécessaires.
Il est également possible de contrôler la durée de vie des données dans le cache, pour cela il suffit de définir le paramètre «inactif» de la directive «proxy_cache_path» qui est de 10 minutes par défaut. Si pendant le temps spécifié dans le paramètre «inactif» il n'y a pas eu d'appel aux données de cache, alors ces données sont supprimées même si le cache n'est pas encore «aigre».
À quoi ressemble ce cache? Il s'agit en fait d'un fichier normal sur le serveur, dont le contenu est écrit:
• clé de cache;
• en-têtes de cache;
• réponse du contenu du backend.
Si tout est clair avec les en-têtes et la réponse du backend, alors il y a un certain nombre de questions sur la «clé de cache». Comment est-il construit et comment peut-il être géré?
Pour décrire le modèle de construction d'une clé de cache dans nginx, il existe une directive proxy_cache_key, dans laquelle une chaîne est spécifiée comme paramètre. Une chaîne peut être constituée de toutes les variables disponibles dans nginx.
Par exemple:
proxy_cache_key $request_method$host$orig_uri:$cookie_some_cookie:$arg_some_arg;
Le symbole «:» entre le paramètre cookie et le paramètre get est utilisé pour éviter les collisions entre les clés de cache, vous pouvez choisir n'importe quel autre symbole de votre choix. Par défaut, nginx utilise la ligne suivante pour générer la clé:
proxy_cache_key $scheme$proxy_host$request_uri;
Il convient de noter les directives suivantes qui vous aideront à gérer votre mise en cache de manière plus flexible:
proxy_cache_valid - Spécifie le temps de mise en cache des réponses. Il est possible d'indiquer le statut spécifique de la réponse, par exemple 200, 302, 404, etc., ou de tout spécifier à la fois en utilisant la construction «any». Si seul le temps de mise en cache est spécifié, nginx mettra par défaut en cache uniquement les statuts 200, 301 et 302.
Un exemple:
proxy_cache_valid 15m; proxy_cache_valid 404 15s;
Dans cet exemple, nous avons défini la durée de vie du cache sur 15 minutes pour les états 200, 301, 302 (nginx les utilise par défaut, car nous n'avons pas spécifié d'état spécifique). La ligne suivante définit le temps de mise en cache sur 15 secondes, uniquement pour les réponses avec un état de 404.
proxy_cache_lock - Cette directive aidera à éviter plusieurs passes vers le backend immédiatement après un ensemble de cache, il suffit de définir la valeur en position «on». Toutes les autres demandes attendront une réponse dans le cache ou un délai d'expiration pour bloquer la demande sur la page. Par conséquent, tous les délais d'attente peuvent être configurés.
proxy_cache_lock_age - Vous permet de définir un délai d'expiration pour une réponse du serveur, après quoi la prochaine requête lui sera envoyée une fois le cache défini. La valeur par défaut est 5 secondes.
proxy_cache_lock_timeout - Définit le temps d'attente pour le verrouillage, après quoi la demande sera envoyée au backend, mais la réponse ne sera pas mise en cache. La valeur par défaut est 5 secondes.
proxy_cache_use_stale - Une autre directive utile qui vous permet de configurer quand il est possible d'utiliser un cache obsolète.
Un exemple:
proxy_cache_use_stale error timeout updating;
Dans ce cas, il utilisera un cache obsolète en cas d'erreur de connexion, d'envoi d'une demande, de lecture d'une réponse du serveur, de dépassement de la limite d'attente pour l'envoi d'une demande, de lecture d'une réponse du serveur, ou si les données du cache sont mises à jour au moment de la demande.
proxy_cache_bypass - Spécifie les conditions dans lesquelles nginx ne prendra pas de réponse du cache, mais redirigera immédiatement la demande vers le backend. Si au moins un des paramètres n'est pas vide et n'est pas égal à «0». Un exemple:
proxy_cache_bypass $cookie_nocache $arg_nocache;
proxy_no_cache - Définit la condition dans laquelle nginx n'enregistrera pas la réponse du backend dans le cache. Le principe de fonctionnement est le même que celui de la directive proxy_cache_bypass.
Problèmes possibles avec la mise en cache des pages
Comme mentionné ci-dessus, avec la mise en cache d'une réponse HTTP, nginx enregistre les en-têtes reçus du backend. Si votre site utilise une session, le cookie de session sera également mis en cache. Tous les utilisateurs qui visitent la page que vous avez eu la chance de mettre en cache recevront vos données personnelles stockées dans la session.
Le prochain défi auquel vous serez confronté est la gestion de la mise en cache. Bien sûr, vous pouvez définir un temps de cache insignifiant de 2 à 5 minutes et cela suffira dans la plupart des cas. Mais cela n'est pas applicable dans toutes les situations, nous allons donc réinventer notre vélo. Maintenant, tout d'abord.
Gestion de la conservation des cookiesLa mise en cache du côté nginx impose certaines restrictions de conception. Par exemple, nous ne pouvons pas utiliser de sessions sur les pages mises en cache, puisque l'utilisateur n'atteint pas le backend, une autre limitation est la livraison de cookies par le backend. Étant donné que nginx met en cache tous les en-têtes, afin d'éviter de stocker la session de quelqu'un d'autre dans le cache, nous devons interdire la livraison de cookies pour les pages mises en cache. La directive proxy_ignore_headers nous y aidera. L'argument répertorie les en-têtes qui doivent être ignorés du backend.
Un exemple:
proxy_ignore_headers "Set-Cookie";
Avec cette ligne, nous ignorons l'installation de cookies à partir du serveur mandaté, c'est-à-dire que l'utilisateur recevra une réponse sans l'en-tête "Set-Cookies". En conséquence, tout ce que le backend a tenté d'écrire dans le cookie sera ignoré côté client, car il ne saura même pas que quelque chose lui était destiné. Cette restriction de cookie doit être prise en compte lors du développement d'une application. Par exemple, pour demander une autorisation, vous pouvez désactiver l'allumage de l'en-tête afin que l'utilisateur reçoive un cookie de session.
Vous devez également prendre en compte la durée de vie de la session, elle peut être consultée dans le paramètre «
session.gc_maxlifetime » de la configuration php.ini. Imaginez que l'utilisateur se connecte au site et commence à consulter le fil d'actualités, toutes les données sont déjà dans le cache nginx. Après un certain temps, l'utilisateur remarque que son autorisation a disparu et il doit à nouveau passer par le processus d'autorisation, bien qu'il soit resté tout le temps sur le site pour regarder les informations. Cela s'est produit parce que sur toutes ses demandes, nginx a renvoyé le résultat du cache sans envoyer de demande au backend. Par conséquent, le backend a décidé que l'utilisateur était inactif et après une heure spécifiée dans «
session.gc_maxlifetime » a supprimé le fichier de session.
Pour éviter que cela ne se produise, nous pouvons émuler des demandes d'arrière-plan. Par exemple, via ajax, envoyez une demande qui sera transmise au backend. Pour passer le cache nginx au backend, il suffit d'envoyer une requête POST, vous pouvez également utiliser la règle de la directive «proxy_cache_bypass», ou tout simplement désactiver le cache de cette page. La demande n'a pas à rendre quelque chose, il peut s'agir d'un fichier avec une seule ligne commençant la session. Le but d'une telle demande est de prolonger la durée de vie de la session pendant que l'utilisateur est sur le site, et nginx donne consciencieusement les données mises en cache à toutes ses demandes.
Gestion du vidage du cacheVous devez d'abord déterminer les exigences, le but que nous essayons d'atteindre. Disons que notre site a une section avec une diffusion de texte des événements sportifs populaires. Lorsque le chargement de la page est donné depuis le cache, tous les nouveaux messages arrivent sur les sockets. Pour que l'utilisateur puisse voir les messages actuels à l'heure actuelle au premier démarrage, plutôt qu'il y a 15 minutes, nous devons être en mesure d'effacer indépendamment le cache nginx à tout moment. En même temps, nginx peut ne pas se trouver sur la même machine que l'application. En outre, l'une des exigences pour une réinitialisation sera la possibilité de supprimer le cache, sur plusieurs pages à la fois.
Avant de commencer à rédiger votre solution, voyons ce que nginx propose immédiatement. Pour réinitialiser le cache, nginx a une directive spéciale appelée "proxy_cache_purge", qui enregistre la condition de réinitialisation du cache. La condition est en fait une ligne normale qui, si elle n'est pas vide et non «0», supprimera le cache par la clé passée. Prenons un petit exemple.
proxy_cache_path /data/nginx/cache keys_zone=cache_zone:10m; map $request_method $purge_method { PURGE 1; default 0; } server { ... location / { proxy_pass http://backend; proxy_cache cache_zone; proxy_cache_key $uri; proxy_cache_purge $purge_method; } }
Un exemple est tiré du site officiel de nginx.La variable $ purge_method est responsable du vidage du cache, qui est une condition pour la directive proxy_cache_purge et est définie sur 0 par défaut. Cela signifie que nginx fonctionne en mode «normal» (il enregistre les réponses du backend). Mais si vous changez la méthode de demande en «PURGE», puis au lieu de mandater la demande pour le backend avec l'enregistrement de la réponse, l'entrée de cache sera supprimée à l'aide de la clé de mise en cache correspondante. Il est également possible de spécifier un masque de suppression en spécifiant un «*» à la fin de la clé de cache. Ainsi, nous n'avons pas besoin de connaître l'emplacement du cache sur le disque et le principe de formation des clés, nginx assume ces responsabilités. Mais cette approche présente également des inconvénients.
- La directive proxy_cache_purge est disponible dans le cadre d'un abonnement commercial.
- Il est uniquement possible de supprimer le cache point par point, ou en utilisant le masque de la forme {clé de cache} "*"
Étant donné que les adresses des pages mises en cache peuvent être complètement différentes, sans parties communes, l'approche avec le masque «*» et la directive «proxy_cache_purge» ne nous convient pas. Reste à rappeler un peu de théorie et à découvrir votre idée préférée.
Nous savons que le cache nginx est un fichier normal sur le serveur. Nous avons spécifié indépendamment le répertoire de stockage des fichiers de cache dans la directive "proxy_cache_path", nous avons même spécifié la logique de formation du chemin d'accès au fichier à partir de ce répertoire en utilisant des "niveaux". La seule chose qui nous manque est la formation correcte de la clé de mise en cache. Mais nous pouvons également le voir dans la directive "proxy_cache_key". Il ne nous reste plus qu'à:
- forment le chemin d'accès complet à la page, exactement comme spécifié dans la directive proxy_cache_key;
- coder la chaîne résultante dans md5;
- créer des répertoires imbriqués en utilisant la règle du paramètre «niveaux».
- Et maintenant, nous avons déjà le chemin d'accès complet au fichier cache sur le serveur. Il ne nous reste plus qu'à supprimer ce fichier. De la partie introductive, nous savons que nginx peut ne pas être situé sur la machine d'application, vous devez donc permettre de supprimer plusieurs adresses à la fois. Encore une fois, nous décrivons l'algorithme:
- Les chemins générés vers les fichiers de cache que nous écrirons dans le fichier;
- Écrivons un simple script bash que nous mettons sur la machine avec l'application. Sa tâche sera de se connecter via ssh au serveur où nous avons mis en cache nginx et de supprimer tous les fichiers de cache spécifiés dans le fichier généré à l'étape 1;
Nous passons de la théorie à la pratique, nous écrirons un petit exemple illustrant notre algorithme de travail.
Étape 1. Génération d'un fichier avec des chemins d'accès au cache.
$urls = [ 'httpGETdomain.ru/news/111/1:2', 'httpGETdomain.ru/news/112/3:4', ]; function to_nginx_cache_path(url) { $nginxHash = md5($url); $firstDir = substr($nginxHash, -1, 1); $secondDir = substr($nginxHash, -3, 2); return "/var/lib/nginx/proxy_cache/$firstDir/$secondDir/$nginxHash"; } // tmp $filePath = tempnam('tmp', 'nginx_cache_'); // $fileStream = fopen($filePath, 'a'); foreach ($urls as $url) { // $cachePath = to_nginx_cache_path($url); // fwrite($fileStream, $cachePath . PHP_EOL); } // fclose($fileStream); // bash exec("/usr/local/bin/cache_remover $filePath");
Veuillez noter que la variable $ urls contient l'url des pages mises en cache, déjà au format proxy_cache_key spécifié dans la configuration nginx. L'URL agit comme une balise pour les entités affichées sur la page. Par exemple, vous pouvez créer une table régulière dans la base de données, où chaque entité sera mappée sur une page spécifique sur laquelle elle est affichée. Ensuite, lorsque vous modifiez des données, nous pouvons faire une sélection sur la table et supprimer le cache de toutes les pages dont nous avons besoin.
Étape 2. Connectez-vous au serveur de cache et supprimez les fichiers de cache.
# , FILE_LIST=`cat $1 | tr ` # ssh SSH=`which ssh` USER= # nginx HOST= # KEY= # SSH , $SSH -i ${KEY} ${USER}@${HOST} # rm -rf rm -f $1 #
Les exemples ci-dessus sont fournis à titre indicatif uniquement, ne les utilisez pas en production. Dans les exemples, les vérifications des paramètres d'entrée et des restrictions de commande sont omises. L'un des problèmes que vous pouvez rencontrer est de limiter la longueur de l'argument à la commande rm. Lorsque vous testez dans un environnement de développement sur de petits volumes, cela peut facilement être manqué, et en production, vous obtenez l'erreur «rm: Liste d'arguments trop longue».
Mise en cache de blocs personnalisée
Résumons ce que nous avons réussi à faire:
- réduit la charge sur le backend;
- Découvrez comment gérer la mise en cache
- appris à vider le cache à tout moment.
Mais tout n'est pas aussi bon que cela puisse paraître à première vue. Maintenant, probablement, sinon tous les premiers, alors précisément chaque deuxième site a une fonctionnalité d'enregistrement / autorisation, après avoir traversé, nous voudrons afficher le nom d'utilisateur quelque part dans l'en-tête. Le bloc avec le nom est unique et devrait afficher le nom d'utilisateur sous lequel nous sommes autorisés. Puisque nginx enregistre la réponse du backend, et dans le cas de la page, c'est le contenu html de la page, le bloc avec les données personnelles sera également mis en cache. Tous les visiteurs du site verront le nom du premier utilisateur qui est passé au backend pour un ensemble de cache.
Par conséquent, le backend ne doit pas donner de blocs dans lesquels se trouvent des informations personnelles afin que ces informations ne tombent pas dans le cache nginx.
Il est nécessaire d'envisager un chargement alternatif de ces parties de la page. Comme toujours, cela peut se faire de plusieurs manières, par exemple, après avoir chargé la page, envoyé une demande ajax et affiché le chargeur à la place du contenu personnel. Une autre façon que nous considérerons aujourd'hui est d'utiliser des balises ssi. Voyons d'abord ce qu'est SSI, puis comment l'utiliser en conjonction avec le cache nginx.
Qu'est-ce que SSI et comment ça marche
SSI (Server-Side includes, server-side inclusions) est un ensemble de commandes intégrées dans une page html qui indique au serveur quoi faire.
Voici une liste de ces commandes (directives):
• if / elif / else / endif - L'opérateur de branchement;
• echo - Affiche les valeurs des variables;
• inclure - Vous permet d'insérer le contenu d'un autre fichier dans le document.
Seule la dernière directive sera discutée. La directive include a deux paramètres:
• fichier - Spécifie le chemin d'accès au fichier sur le serveur. Concernant le répertoire courant;
• virtuel - Indique le chemin d'accès virtuel au document sur le serveur.
Nous nous intéressons au paramètre «virtuel», car spécifier le chemin complet du fichier sur le serveur n'est pas toujours pratique, ou dans le cas d'une architecture distribuée, le fichier sur le serveur n'est tout simplement pas là. Exemple de directive:
Pour que nginx démarre le traitement des insertions ssi, vous devez modifier l'emplacement comme suit:
location / { ssi on; ... }
Désormais, toutes les demandes traitées par l'emplacement «/» pourront effectuer des insertions ssi.
Comment notre demande passera-t-elle par tout ce schéma?
- le client demande la page;
- Nginx proxy la demande pour le backend;
- le backend donne la page avec des insertions ssi;
- le résultat est stocké dans le cache;
- Nginx «interroge» les blocs manquants;
- La page résultante est envoyée au client.
Comme vous pouvez le voir dans les étapes, les constructions ssi entreront dans le cache nginx, ce qui permettra de ne pas mettre en cache les blocs personnels, et une page html prête à l'emploi avec toutes les insertions sera envoyée au client. Ici, notre chargement fonctionne, nginx demande indépendamment les blocs de page manquants. Mais comme toute autre solution, cette approche a ses avantages et ses inconvénients. Imaginez qu'il y ait plusieurs blocs sur la page qui devraient être affichés différemment selon l'utilisateur, puis chacun de ces blocs sera remplacé par un insert ssi. Nginx, comme prévu, demandera chacun de ces blocs au backend, c'est-à-dire qu'une requête de l'utilisateur générera immédiatement plusieurs requêtes pour le backend, ce que je ne voudrais pas du tout.
Se débarrasser des requêtes backend persistantes via ssi
Pour résoudre ce problème, le module nginx «ngx_http_memcached_module» nous aidera. Le module permet de recevoir des valeurs du serveur memcached. L'écriture dans le module ne fonctionnera pas, le serveur d'applications devrait s'en occuper. Prenons un petit exemple de configuration de nginx en conjonction avec un module:
server { location /page { set $memcached_key "$uri"; memcached_pass 127.0.0.1:11211; error_page 404 502 504 = @fallback; } location @fallback { proxy_pass http://backend; } }
Dans la variable $ memcache_key, nous avons spécifié la clé par laquelle nginx tentera d'obtenir les données de memcache. Les paramètres de connexion au serveur memcache sont définis dans la directive memcached_pass. La connexion peut être spécifiée de plusieurs manières:
• nom de domaine;
memcached_pass cache.domain.ru;
• adresse IP et port;
memcached_pass localhost:11211;
• prise unix;
memcached_pass unix:/tmp/memcached.socket;
• directive amont.
upstream cachestream { hash $request_uri consistent; server 10.10.1.1:11211; server 10.10.1.2:11211; } location / { ... memcached_pass cachestream; ... }
Si nginx a réussi à obtenir une réponse du serveur de cache, il la donne au client. S'il n'y a pas de données dans le cache, la demande sera envoyée au backend via «@fallback». Cette petite configuration du module memcached sous nginx nous aidera à réduire le nombre de demandes de passage pour le backend des insertions ssi.
Nous espérons que cet article a été utile et que nous avons pu montrer l'une des façons d'optimiser la charge sur le serveur, de considérer les principes de base de la configuration de la mise en cache nginx et de résoudre les problèmes qui se posent lors de son utilisation.