---
title: "FAQ OpenResty | Chargement dynamique, NYI et mise en cache de Shared Dict - API7.ai"
description: "Cet article explique le chargement dynamique, les opérations bloquantes, NYI, l'amont dynamique et la mise en cache des dictionnaires partagés."
url: "https://api7.ai/fr/learning-center/openresty/faq-4"
---

# FAQ OpenResty | Chargement dynamique, NYI et mise en cache de Shared Dict

API7.ai

January 19, 2023

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

La série d'articles sur OpenResty a été mise à jour jusqu'à présent, et la partie sur l'optimisation des performances est tout ce que nous avons appris. Félicitations à vous pour ne pas avoir pris de retard, pour continuer à apprendre et à pratiquer activement, et pour laisser vos réflexions avec enthousiasme.

Nous avons collecté de nombreuses questions plus typiques et intéressantes, et voici un aperçu de cinq d'entre elles.

## Question 1 : Comment accomplir le chargement dynamique des modules Lua ?

> **Description :** J'ai une question sur le chargement dynamique implémenté dans OpenResty. Comment puis-je utiliser la fonction `loadstring` pour terminer le chargement d'un nouveau fichier après qu'il a été remplacé ? Je comprends que `loadstring` ne peut charger que des chaînes de caractères, donc si je veux recharger un fichier/module Lua, comment puis-je le faire dans OpenResty ?

Comme nous le savons, `loadstring` est utilisé pour charger une chaîne de caractères, tandis que `loadfile` peut charger un fichier spécifié, par exemple : `loadfile("foo.lua")`. Ces deux commandes atteignent le même résultat. Quant à la manière de charger des modules Lua, voici un exemple :

```
resty -e 'local s = [[
local ngx = ngx
local _M = {}
function _M.f()
    ngx.say("hello world")
end
return _M
]]
local lua = loadstring(s)
local ret, func = pcall(lua)
func.f()'
```

Le contenu de la chaîne `s` est un module Lua complet. Ainsi, lorsque vous détectez un changement dans le code de ce module, vous pouvez redémarrer le chargement avec `loadstring` ou `loadfile`. De cette manière, les fonctions et variables qu'il contient seront mises à jour en conséquence.

Pour aller plus loin, vous pouvez également encapsuler la récupération des changements et le rechargement dans une fonction appelée `code_loader`.

```
local func = code_loader(name)
```

Cela rend les mises à jour de code beaucoup plus concises. En même temps, `code_loader` utilise généralement un cache `lru` pour mettre en cache `s` afin d'éviter d'appeler `loadstring` à chaque fois.

## Question 2 : Pourquoi OpenResty n'interdit-il pas les opérations bloquantes ?

> **Description :** Au fil des années, je me suis toujours demandé, puisque ces appels bloquants sont officiellement déconseillés, pourquoi ne pas simplement les désactiver ? Ou ajouter un indicateur pour laisser l'utilisateur choisir de les désactiver ?

Voici mon opinion personnelle. Premièrement, parce que l'écosystème autour d'OpenResty n'est pas parfait, parfois nous devons appeler des bibliothèques bloquantes pour implémenter certaines fonctionnalités. Par exemple, avant la version 1.15.8, vous deviez utiliser la bibliothèque Lua `os.execute` au lieu de `lua-resty-shell` pour appeler des commandes externes. Par exemple, dans OpenResty, la lecture et l'écriture de fichiers ne sont encore possibles qu'avec la bibliothèque Lua I/O, et il n'existe pas d'alternative non bloquante.

Deuxièmement, OpenResty est très prudent concernant de telles optimisations. Par exemple, `lua-resty-core` a été développé pendant longtemps, mais il n'a jamais été activé par défaut, nécessitant que vous appeliez manuellement `require 'resty.core'`. Il a été activé jusqu'à la dernière version 1.15.8.

Enfin, les mainteneurs d'OpenResty préfèrent standardiser les appels bloquants en générant automatiquement du code Lua hautement optimisé via le compilateur et le DSL. Ainsi, il n'y a pas d'effort pour faire quelque chose comme des options d'indicateur sur la plateforme OpenResty elle-même. Bien sûr, je ne suis pas sûr que cette direction puisse résoudre le problème.

Du point de vue d'un développeur externe, le problème plus pratique est de savoir comment éviter de tels blocages. Nous pouvons étendre les outils de détection de code Lua, comme `luacheck`, pour trouver et alerter sur les opérations bloquantes courantes, ou nous pouvons désactiver ou réécrire de manière intrusive certaines fonctions directement en réécrivant `_G`, par exemple :

```
resty -e '_G.ngx.print = function()
ngx.say("hello")
end
ngx.print()'

# hello
```

Avec cet exemple de code, vous pouvez réécrire directement la fonction `ngx.print`.

## Question 3 : L'opération de NYI de LuaJIT a-t-elle un impact significatif sur les performances ?

> **Description :** `loadstring` montre `never` dans la liste NYI de LuaJIT. Cela aura-t-il un grand impact sur les performances ?

Concernant le NYI de LuaJIT, nous n'avons pas besoin d'être trop stricts. Pour les opérations qui peuvent être JIT, l'approche JIT est naturellement la meilleure ; mais pour les opérations qui ne peuvent pas encore être JIT, nous pouvons continuer à les utiliser.

Pour l'optimisation des performances, nous devons adopter une approche scientifique basée sur des statistiques, c'est ce que fait l'échantillonnage des graphes de flammes. L'optimisation prématurée est la racine de tous les maux. Nous n'avons besoin d'optimiser que le code chaud qui effectue de nombreux appels et consomme beaucoup de CPU.

Revenons à `loadstring`, nous ne l'appellerons pour recharger que lorsque le code change, pas à chaque requête, donc ce n'est pas une opération fréquente. À ce stade, nous n'avons pas à nous inquiéter de son impact sur les performances globales du système.

En lien avec le deuxième problème de blocage, dans OpenResty, nous invoquons parfois également des opérations d'E/S de fichiers bloquantes pendant les phases `init` et `init worker`. Cette opération est plus compromise en termes de performances que NYI, mais comme elle n'est effectuée qu'une seule fois au démarrage du service, elle est acceptable.

Comme toujours, l'optimisation des performances doit être vue d'un point de vue macro, un point auquel vous devez prêter une attention particulière. Sinon, en vous focalisant sur un détail particulier, vous risquez d'optimiser pendant longtemps sans obtenir un bon effet.

## Question 4 : Puis-je implémenter moi-même un upstream dynamique ?

> **Description :** Pour l'upstream dynamique, mon approche consiste à configurer 2 upstreams pour un service, à sélectionner différents upstreams selon les conditions de routage, et à modifier directement l'IP dans l'upstream lorsque l'IP de la machine change. Y a-t-il un inconvénient ou un piège dans cette approche par rapport à l'utilisation directe de `balancer_by_lua` ?

L'avantage de `balancer_by_lua` est qu'il permet à l'utilisateur de choisir l'algorithme d'équilibrage de charge, par exemple, s'il faut utiliser `roundrobin` ou `chash`, ou tout autre algorithme implémenté par l'utilisateur, ce qui est flexible et performant.

Si vous le faites de la manière des règles de routage, c'est la même chose en termes de résultat. Mais la vérification de santé de l'upstream doit être implémentée par vous, ce qui ajoute beaucoup de travail supplémentaire.

Nous pouvons également étendre cette question en demandant comment nous devrions implémenter ce scénario pour `abtest`, qui nécessite un upstream différent.

Vous pouvez décider quel upstream utiliser dans la phase `balancer_by_lua` en fonction de `uri`, `host`, `parameters`, etc. Vous pouvez également utiliser des passerelles API pour transformer ces jugements en règles de routage, en décidant quelle route utiliser dans la phase initiale `access`, puis en trouvant l'upstream spécifié via la relation de liaison entre la route et l'upstream. C'est une approche courante des passerelles API, et nous en parlerons plus en détail plus tard dans la section pratique.

## Question 5 : La mise en cache de `shared dict` est-elle obligatoire ?

> **Description :**
>
> Dans les applications de production réelles, je pense que la couche de cache `shared dict` est indispensable. Il semble que tout le monde ne se souvienne que des avantages du cache `lru`, aucune restriction sur le format des données, pas besoin de désérialiser, pas besoin de calculer l'espace mémoire en fonction du volume k/v, pas de contention entre les workers, pas de verrous de lecture/écriture et des performances élevées.
>
> Cependant, ne négligez pas que l'un de ses points faibles les plus fatals est que le cycle de vie du cache `lru` suit le `Worker`. Chaque fois que NGINX recharge, cette partie du cache sera complètement perdue, et à ce moment-là, s'il n'y a pas de `shared dict`, la source de données `L3` sera saturée en quelques minutes.
>
> Bien sûr, c'est le cas d'une concurrence plus élevée, mais puisque le cache est utilisé, le volume d'affaires n'est certainement pas faible, ce qui signifie que l'analyse mentionnée ci-dessus s'applique toujours. Si j'ai raison dans cette perspective ?

Dans certains cas, il est vrai que, comme vous l'avez dit, le `shared dict` n'est pas perdu lors du rechargement, donc il est nécessaire. Mais il y a un cas particulier où seul le cache `lru` est acceptable si toutes les données sont activement disponibles depuis `L3`, la source de données, dans la phase `init` ou `init_worker`.

Par exemple, si la passerelle API open source [APISIX](https://apisix.apache.org/) a sa source de données dans `etcd`, elle ne récupère les données que depuis `etcd`. Elle les met en cache dans le cache `lru` pendant la phase `init_worker`, et les mises à jour ultérieures du cache sont activement récupérées via le mécanisme `watch` de `etcd`. De cette manière, même si NGINX recharge, il n'y aura pas de ruée vers le cache.

Ainsi, nous pouvons avoir des préférences dans le choix de la technologie, mais ne généralisons pas de manière absolue car aucune solution unique ne peut s'adapter à tous les scénarios de mise en cache. C'est une excellente manière de construire une solution minimale viable selon les besoins du scénario réel, puis de l'augmenter progressivement.

Share article link

Share

Related Posts

[![FAQ OpenResty | Comment OpenResty est utilisé en pratique](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [FAQ OpenResty | Comment OpenResty est utilisé en pratique](https://api7.ai/learning-center/openresty/faq-5)

[![Partie 3 : Comment construire une passerelle API pour microservices avec OpenResty](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [Partie 3 : Comment construire une passerelle API pour microservices avec OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [Question 1 : Comment accomplir le chargement dynamique des modules Lua ?](#question-1--comment-accomplir-le-chargement-dynamique-des-modules-lua-)
- [Question 2 : Pourquoi OpenResty n'interdit-il pas les opérations bloquantes ?](#question-2--pourquoi-openresty-ninterdit-il-pas-les-op%C3%A9rations-bloquantes-)
- [Question 3 : L'opération de NYI de LuaJIT a-t-elle un impact significatif sur les performances ?](#question-3--lop%C3%A9ration-de-nyi-de-luajit-a-t-elle-un-impact-significatif-sur-les-performances-)
- [Question 4 : Puis-je implémenter moi-même un upstream dynamique ?](#question-4--puis-je-impl%C3%A9menter-moi-m%C3%AAme-un-upstream-dynamique-)
- [Question 5 : La mise en cache de `shared dict` est-elle obligatoire ?](#question-5--la-mise-en-cache-de-shared-dict-est-elle-obligatoire-)
