La función asesina de OpenResty: Dinámica
API7.ai
January 12, 2023
Hasta ahora, casi hemos terminado con el contenido relacionado con el rendimiento de OpenResty. Dominar y aplicar de manera flexible estas técnicas de optimización puede mejorar enormemente el rendimiento de nuestro código. Hoy, en la última parte de la optimización del rendimiento, aprendamos sobre una capacidad comúnmente subestimada en OpenResty: "dinámico".
Comencemos por ver qué es lo dinámico y cómo se relaciona con el rendimiento. Dinámico en este contexto significa que los programas pueden modificar parámetros, configuraciones e incluso su código en tiempo de ejecución, sin necesidad de recargar. Específicamente, en NGINX y OpenResty, puedes cambiar el upstream, los certificados SSL y los umbrales de limitación de tasa sin reiniciar el servicio, logrando así la dinámica. En cuanto a la relación entre lo dinámico y el rendimiento, es evidente que si este tipo de operaciones no se pueden realizar de manera dinámica, entonces los frecuentes reinicios de los servicios de NGINX naturalmente resultarán en una pérdida de rendimiento.
Sin embargo, sabemos que la versión de código abierto de NGINX no admite características dinámicas, por lo que tienes que cambiar el upstream y los certificados SSL modificando el archivo de configuración y reiniciando el servicio para que surtan efecto. NGINX Plus (la versión comercial de NGINX) proporciona algunas capacidades dinámicas, y puedes usar la API REST para actualizar, pero esto es una mejora menos que radical en el mejor de los casos.
En OpenResty, estas limitaciones no existen, y lo dinámico es la característica principal de OpenResty. Puedes preguntarte por qué OpenResty, basado en NGINX, puede soportar lo dinámico. La razón es simple: la lógica de NGINX se realiza a través de módulos en C, mientras que OpenResty se realiza a través de Lua, un lenguaje de scripting. Una de las ventajas de los lenguajes de scripting es que pueden cambiarse dinámicamente en tiempo de ejecución.
Cargar código dinámicamente
Aquí está cómo cargar dinámicamente código Lua en OpenResty.
resty -e 'local s = [[ngx.say("hello world")]]
local func, err = loadstring(s)
func()'
Podemos ver que en solo unas pocas líneas de código, podemos convertir una cadena en una función Lua y hacer que se ejecute. Analicemos más de cerca estas líneas de código:
- Primero, declaramos una cadena cuyo contenido es un fragmento de código Lua que imprime
hello world
; - Luego, usando la función
loadstring
en Lua, convertimos el objeto de cadena en el objeto de funciónfunc
. - Finalmente, agregamos paréntesis al nombre de la función para ejecutar
func
e imprimirhello world
.
Por supuesto, también podemos extender funciones más interesantes y prácticas basadas en este código. A continuación, te llevaré a probarlo.
Función 1: FaaS
Primero está FaaS (Function-as-a-Service), que recientemente ha sido una dirección tecnológica muy popular. Veamos cómo implementarlo en OpenResty. En el código mencionado anteriormente, la cadena es un código Lua. También podemos cambiarlo a una función Lua:
local s = [[
return function()
ngx.say("hello world")
end
]]
Como dijimos, las funciones son ciudadanos de primera clase en Lua, y este código devuelve una función anónima. Al ejecutar esta función anónima, usamos pcall
para proporcionar una capa de protección. pcall
ejecutará la función en modo protegido y capturará la excepción. Si es normal, devolverá true
y el resultado de la ejecución. Si falla, devolverá false
y la información de error, que es el siguiente código:
local func1, err = loadstring(s)
local ret, func = pcall(func1)
Naturalmente, si combinas las dos partes anteriores, obtendrás un ejemplo completo y operable:
resty -e 'local s = [[
return function()
ngx.say("hello world")
end
]]
local func1 = loadstring(s)
local ret, func = pcall(func1)
func()'
Para ir un paso más allá, podemos cambiar la cadena s
que contiene funciones a una forma que los usuarios puedan especificar y agregar las condiciones para su ejecución. Este es el prototipo de FaaS. Aquí, proporciono una implementación completa. Si estás interesado en FaaS y deseas continuar tu investigación, sigue el enlace para aprender más.
Función 2: Edge Computing
Lo dinámico de OpenResty se puede usar para FaaS, haciendo que la dinámica del lenguaje de scripting se refine al nivel de la función, y juegue un papel dinámico en el edge computing.
Debido a estas ventajas, podemos extender los tentáculos de OpenResty desde los campos de la puerta de enlace API, WAF (Web Application Firewall), servidor web y otros servidores a los nodos edge más cercanos a los usuarios, como dispositivos IoT, nodos edge de CDN, routers, etc.
Esto no es solo una fantasía. OpenResty se ha utilizado ampliamente en los campos mencionados anteriormente. Tomando como ejemplo los nodos edge de CDN, Cloudflare, el mayor usuario de OpenResty, ha realizado el control dinámico de los nodos edge de CDN con la ayuda de las características dinámicas de OpenResty durante mucho tiempo.
El enfoque de Cloudflare es similar al principio de cargar código dinámicamente mencionado anteriormente, que se puede dividir aproximadamente en los siguientes pasos:
- Primero, obtener los archivos de código cambiados del clúster de base de datos clave-valor. El método puede ser la consulta de temporizador en segundo plano o el modo "publicar-suscribir" para monitorear;
- Luego, reemplazar el archivo antiguo en el disco local con el archivo de código actualizado y actualizar la caché cargada en la memoria usando los métodos
loadstring
ypcall
;
De esta manera, la próxima solicitud del cliente que se procese pasará por la lógica del código actualizado. Por supuesto, la aplicación práctica debe considerar más detalles que los pasos anteriores, como el control de versiones y la reversión, el manejo de excepciones, la interrupción de la red, el reinicio del nodo edge, etc., pero el proceso general no cambia.
Si movemos el enfoque de Cloudflare desde los nodos edge de CDN a otros escenarios edge, podemos asignar dinámicamente mucha capacidad de cálculo a los dispositivos de nodos edge. Esto no solo puede aprovechar al máximo la capacidad de cálculo de los nodos edge, sino que también permite que los usuarios obtengan respuestas más rápidas a las solicitudes porque el nodo edge procesará los datos originales y luego los resumirá en el servidor remoto, lo que reduce enormemente la cantidad de datos transmitidos.
Sin embargo, para hacer un excelente trabajo en FaaS y edge computing, lo dinámico de OpenResty es solo una buena base. También necesitas considerar la mejora de tu ecosistema circundante y la participación de los fabricantes, lo cual no es solo una categoría técnica.
Upstream Dinámico
Ahora, volvamos nuestros pensamientos a OpenResty para ver cómo lograr un upstream dinámico. lua-resty-core
proporciona una biblioteca de ngx.balancer
para configurar el upstream. Debe colocarse en la etapa balancer
de OpenResty para ejecutarse:
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 función set_current_peer
configura la dirección IP y el puerto del upstream. Sin embargo, nos gustaría señalar que aquí no se admite el nombre de dominio. Necesitamos usar la biblioteca lua-resty-dns
para hacer una capa de análisis para el nombre de dominio y la IP.
Sin embargo, ngx.balancer
es relativamente bajo. Aunque se puede usar para configurar el upstream, la realización del upstream dinámico está lejos de ser simple. Por lo tanto, se necesitan dos funciones delante de ngx.balancer
:
- Primero, decidir si el algoritmo de selección de upstream es
consistent hash
oroundrobin
; - Segundo, el mecanismo de verificación de salud del upstream, que necesita eliminar el upstream no saludable y volver a unirlo cuando el upstream no saludable se vuelva saludable.
La biblioteca oficial de OpenResty lua-resty-balancer
contiene dos tipos de algoritmos: resty.chash
y resty.roundrobin
para completar la primera función, y tiene lua-resty-upstream-healthcheck
para intentar completar la segunda función.
Sin embargo, todavía hay dos problemas.
El primer punto es la falta de implementación completa de la última milla. Convertir ngx.balancer
, lua-resty-balancer
y lua-resty-upstream-healthcheck
para combinar las funciones de upstream dinámico, pero todavía se necesita algo de trabajo, lo que detiene a la mayoría de los desarrolladores.
En segundo lugar, la implementación de lua-resty-upstream-healthcheck
no está completa. Solo hay verificación de salud pasiva pero no verificación de salud activa.
Las solicitudes de los clientes activan la verificación de salud pasiva aquí y luego analizan el valor de retorno del upstream como una condición para determinar si la salud es buena. Si no hay solicitudes de clientes, no se sabe si el upstream está saludable. Las verificaciones de salud activas pueden remediar este defecto. Utiliza ngx.timer
para sondear periódicamente la interfaz de upstream especificada para detectar el estado de salud.
Por lo tanto, en la práctica real, generalmente recomendamos usar lua-resty-healthcheck
para completar las verificaciones de salud del upstream. Su ventaja es que incluye verificaciones de salud activas y pasivas, y ha sido verificado en múltiples proyectos con mayor confiabilidad.
Además, la puerta de enlace API de microservicios emergente Apache APISIX ha realizado una implementación completa de upstream dinámico basado en lua-resty-upstream-healthcheck
. Podemos referirnos a su implementación. Hay solo 400 líneas de código en total. Puedes despegarlo fácilmente y ponerlo en tu proyecto para usarlo.
Resumen
Con respecto a lo dinámico de OpenResty, ¿en qué áreas y escenarios puedes aprovecharlo? También puedes expandir los contenidos de cada parte introducida en este capítulo para un análisis más detallado y profundo.
Te invitamos a compartir este artículo y aprender y progresar con más personas.