La fonctionnalité phare d'OpenResty : le dynamisme

API7.ai

January 12, 2023

OpenResty (NGINX + Lua)

Jusqu'à présent, nous avons presque terminé le contenu lié aux performances d'OpenResty. Maîtriser et appliquer de manière flexible ces techniques d'optimisation peut grandement améliorer les performances de notre code. Aujourd'hui, dans la dernière partie de l'optimisation des performances, apprenons-en plus sur une capacité souvent sous-estimée dans OpenResty : la "dynamique".

Commençons par examiner ce qu'est la dynamique et comment elle est liée aux performances. La dynamique, dans ce contexte, signifie que les programmes peuvent modifier des paramètres, des configurations, et même leur code à l'exécution, sans rechargement. Plus précisément, dans NGINX et OpenResty, vous pouvez changer les serveurs en amont (upstream), les certificats SSL, et les seuils de limitation de débit sans redémarrer le service, ce qui permet d'atteindre la dynamique. Quant à la relation entre la dynamique et les performances, il est clair que si ces types d'opérations ne peuvent pas être effectués de manière dynamique, alors des rechargements fréquents des services NGINX entraîneront naturellement une perte de performance.

Cependant, nous savons que la version open source de NGINX ne supporte pas les fonctionnalités dynamiques, donc vous devez modifier les serveurs en amont et les certificats SSL en modifiant le fichier de configuration et en redémarrant le service pour qu'ils prennent effet. NGINX Plus (la version commerciale de NGINX) fournit certaines capacités dynamiques, et vous pouvez utiliser l'API REST pour les mettre à jour, mais cela reste une amélioration peu radicale.

Dans OpenResty, ces contraintes n'existent pas, et la dynamique est une fonctionnalité clé d'OpenResty. Vous vous demandez peut-être pourquoi OpenResty, basé sur NGINX, peut supporter la dynamique. La raison est simple : la logique de NGINX est implémentée via des modules en C, tandis qu'OpenResty est implémenté via Lua, un langage de script. L'un des avantages des langages de script est qu'ils peuvent être modifiés dynamiquement à l'exécution.

Charger du code dynamiquement

Voici comment charger dynamiquement du code Lua dans OpenResty.

resty -e 'local s = [[ngx.say("hello world")]]
local func, err = loadstring(s)
func()'

Nous pouvons voir qu'en quelques lignes de code seulement, nous pouvons transformer une chaîne de caractères en une fonction Lua et l'exécuter. Examinons de plus près ces lignes de code :

  • Tout d'abord, nous déclarons une chaîne dont le contenu est un morceau de code Lua qui affiche hello world ;
  • Ensuite, en utilisant la fonction loadstring de Lua, nous transformons l'objet chaîne en un objet fonction func.
  • Enfin, nous ajoutons des parenthèses au nom de la fonction pour exécuter func et afficher hello world.

Bien sûr, nous pouvons également étendre des fonctions plus intéressantes et pratiques à partir de ce code. Ensuite, je vais vous montrer comment faire.

Fonction 1 : FaaS

La première est FaaS (Function-as-a-Service), qui est récemment devenue une direction technologique très populaire. Voyons comment l'implémenter dans OpenResty. Dans le code mentionné précédemment, la chaîne est un code Lua. Nous pouvons également la transformer en une fonction Lua :

local s = [[
 return function()
     ngx.say("hello world")
end
]]

Comme nous l'avons dit, les fonctions sont des citoyens de première classe en Lua, et ce code retourne une fonction anonyme. Lors de l'exécution de cette fonction anonyme, nous utilisons pcall pour fournir une couche de protection. pcall exécutera la fonction en mode protégé et capturera les exceptions. Si tout se passe bien, il retournera true et le résultat de l'exécution. En cas d'échec, il retournera false et des informations d'erreur, comme le montre le code suivant :

local func1, err = loadstring(s)
local ret, func = pcall(func1)

Naturellement, si vous combinez les deux parties ci-dessus, vous obtiendrez un exemple complet et opérationnel :

resty -e 'local s = [[
 return function()
    ngx.say("hello world")
end
]]
local  func1 = loadstring(s)
local ret, func = pcall(func1)
func()'

Pour aller plus loin, nous pouvons changer la chaîne s contenant des fonctions en une forme que les utilisateurs peuvent spécifier et ajouter les conditions de son exécution. C'est le prototype de FaaS. Ici, je fournis une implémentation complète. Si vous êtes intéressé par FaaS et souhaitez poursuivre vos recherches, consultez le lien pour en savoir plus.

Fonction 2 : Edge Computing

La dynamique d'OpenResty peut être utilisée pour FaaS, rendant la dynamique du langage de script raffinée au niveau de la fonction, et jouant un rôle dynamique dans l'edge computing.

Grâce à ces avantages, nous pouvons étendre les tentacules d'OpenResty des domaines de la passerelle API, du WAF (Web Application Firewall), du serveur web, et d'autres extrémités de serveur aux nœuds edge les plus proches des utilisateurs, tels que les appareils IoT, les nœuds edge CDN, les routeurs, etc.

Ce n'est pas qu'une fantaisie. OpenResty est déjà largement utilisé dans les domaines mentionnés ci-dessus. Prenons l'exemple des nœuds edge CDN, Cloudflare, le plus grand utilisateur d'OpenResty, a réalisé depuis longtemps un contrôle dynamique des nœuds edge CDN grâce aux caractéristiques dynamiques d'OpenResty.

L'approche de Cloudflare est similaire au principe de chargement dynamique de code mentionné ci-dessus, et peut être grossièrement divisée en les étapes suivantes :

  • Tout d'abord, obtenir les fichiers de code modifiés à partir du cluster de base de données clé-valeur. La méthode peut être un sondage périodique en arrière-plan ou un mode "publish-subscribe" pour surveiller ;
  • Ensuite, remplacer l'ancien fichier sur le disque local par le fichier de code mis à jour et mettre à jour le cache chargé en mémoire en utilisant les méthodes loadstring et pcall ;

Ainsi, la prochaine requête client à traiter passera par la logique de code mise à jour. Bien sûr, l'application pratique doit prendre en compte plus de détails que les étapes ci-dessus, comme le contrôle de version et le retour en arrière, la gestion des exceptions, les interruptions de réseau, le redémarrage des nœuds edge, etc., mais le processus global reste inchangé.

Si nous transposons l'approche de Cloudflare des nœuds edge CDN à d'autres scénarios edge, nous pouvons attribuer dynamiquement beaucoup de puissance de calcul aux appareils des nœuds edge. Cela permet non seulement d'utiliser pleinement la puissance de calcul des nœuds edge, mais aussi de permettre aux utilisateurs d'obtenir des réponses plus rapides à leurs requêtes, car le nœud edge traitera les données originales avant de les résumer au serveur distant, ce qui réduit considérablement la quantité de données transmises.

Cependant, pour exceller dans FaaS et l'edge computing, la dynamique d'OpenResty n'est qu'une bonne base. Vous devez également prendre en compte l'amélioration de votre écosystème environnant et la participation des fabricants, ce qui ne relève pas uniquement de la technique.

Upstream Dynamique

Maintenant, revenons à OpenResty pour voir comment réaliser un upstream dynamique. lua-resty-core fournit une bibliothèque ngx.balancer pour configurer l'upstream. Elle doit être placée dans la phase balancer d'OpenResty pour fonctionner :

balancer_by_lua_block {
    local balancer = require "ngx.balancer"
    local host = "127.0.0.2"
    local port = 8080

    local ok, err = balancer.set_current_peer(host, port)
    if not ok then
        ngx.log(ngx.ERR, "failed to set the current peer: ", err)
        return ngx.exit(500)
    end
}

La fonction set_current_peer configure l'adresse IP et le port de l'upstream. Cependant, nous tenons à souligner que les noms de domaine ne sont pas supportés ici. Nous devons utiliser la bibliothèque lua-resty-dns pour effectuer une analyse entre le nom de domaine et l'IP.

Cependant, ngx.balancer est relativement bas niveau. Bien qu'il puisse être utilisé pour configurer l'upstream, la réalisation d'un upstream dynamique est loin d'être simple. Par conséquent, deux fonctions sont nécessaires avant ngx.balancer :

  • Tout d'abord, décider si l'algorithme de sélection de l'upstream est consistent hash ou roundrobin ;
  • Ensuite, le mécanisme de vérification de la santé de l'upstream, qui doit éliminer les upstream malsains et les réintégrer lorsqu'ils redeviennent sains.

La bibliothèque officielle d'OpenResty lua-resty-balancer contient deux types d'algorithmes : resty.chash et resty.roundrobin pour accomplir la première fonction, et elle a lua-resty-upstream-healthcheck pour essayer d'accomplir la seconde fonction.

Cependant, il reste deux problèmes.

Le premier point est le manque d'implémentation complète du dernier kilomètre. Combiner ngx.balancer, lua-resty-balancer, et lua-resty-upstream-healthcheck pour réaliser les fonctions d'upstream dynamique nécessite encore un certain travail, ce qui décourage la plupart des développeurs.

Deuxièmement, l'implémentation de lua-resty-upstream-healthcheck n'est pas complète. Il n'y a que la vérification passive de la santé, mais pas la vérification active.

La vérification passive de la santé est déclenchée ici par les requêtes des clients, puis analyse la valeur de retour de l'upstream comme condition pour déterminer si la santé est bonne. Si aucune requête client n'est présente, la santé de l'upstream est inconnue. La vérification active de la santé peut remédier à ce défaut. Elle utilise ngx.timer pour interroger périodiquement l'interface spécifiée de l'upstream afin de détecter l'état de santé.

Par conséquent, dans la pratique, nous recommandons généralement d'utiliser lua-resty-healthcheck pour effectuer les vérifications de santé de l'upstream. Son avantage est qu'il inclut à la fois les vérifications actives et passives de la santé, et a été validé dans plusieurs projets avec une fiabilité plus élevée.

De plus, la passerelle API microservices émergente Apache APISIX a réalisé une implémentation complète de l'upstream dynamique basée sur lua-resty-upstream-healthcheck. Nous pouvons nous référer à son implémentation. Il y a seulement 400 lignes de code au total. Vous pouvez facilement l'extraire et l'intégrer dans votre projet pour l'utiliser.

Résumé

En ce qui concerne la dynamique d'OpenResty, dans quels domaines et scénarios pouvez-vous en tirer parti ? Vous pouvez également développer les contenus de chaque partie introduite dans ce chapitre pour une analyse plus détaillée et approfondie.

Vous êtes invités à partager cet article et à apprendre et progresser avec plus de personnes.

Share article link