Cartucho de Tarantool: Fragmentación del backend de Lua en tres líneas


En Mail.ru Group, tenemos Tarantool, un servidor de aplicaciones basado en Lua y una base de datos unida. Es rápido y elegante, pero los recursos de un solo servidor siempre son limitados. La escala vertical tampoco es la panacea. Es por eso que Tarantool tiene algunas herramientas para el escalado horizontal, o el módulo vshard [1] . Le permite distribuir datos a través de múltiples servidores, pero tendrá que jugar con ellos por un tiempo para configurarlos y atornillar la lógica empresarial.

Buenas noticias: obtuvimos nuestra parte de baches (por ejemplo, [2] , [3] ) y creamos otro marco, que simplifica significativamente la solución a este problema.

Tarantool Cartridge es el nuevo marco para desarrollar sistemas distribuidos complejos. Le permite concentrarse en escribir lógica empresarial en lugar de resolver problemas de infraestructura. Debajo del corte, le diré cómo funciona este marco y cómo podría ayudar a escribir servicios distribuidos.

Entonces, ¿cuál es exactamente el problema?


Tenemos Tarantool y vshard, ¿qué más queremos?

Primero, es una cuestión de conveniencia. Vshard está configurado en tablas Lua. Pero para que un sistema distribuido de varios procesos de Tarantool funcione correctamente, la configuración debe ser la misma en todas partes. Nadie querría hacerlo manualmente, por lo que se utilizan todo tipo de scripts, Ansible y sistemas de implementación.

El propio cartucho gestiona la configuración vshard basada en su propia configuración distribuida . De hecho, es un archivo YAML simple, y su copia se almacena en cada instancia de Tarantool. En otras palabras, el marco monitorea su configuración para que sea igual en todas partes.

Segundo, nuevamente es una cuestión de conveniencia. La configuración de Vshard no está relacionada con el desarrollo de la lógica de negocios y solo distrae a un desarrollador de su trabajo. Cuando discutimos la arquitectura de un proyecto, lo más probable es que se trate de componentes separados y su interacción. Es demasiado pronto para pensar siquiera en implementar un clúster para 3 centros de datos.

Resolvimos estos problemas una y otra vez y, en algún momento, logramos desarrollar un enfoque para simplificar el trabajo con la aplicación durante todo su ciclo de vida: creación, desarrollo, pruebas, CI / CD, mantenimiento.

Cartridge presenta el concepto de roles para cada proceso de Tarantool. Los roles permiten al desarrollador concentrarse en escribir código. Todos los roles disponibles en el proyecto se pueden ejecutar en la única instancia de Tarantool, y esto sería suficiente para la prueba.

Características clave del cartucho de Tarantool:

  • orquestación automatizada de clústeres;
  • funcionalidad de aplicación expandida con nuevos roles;
  • plantilla de aplicación para desarrollo y despliegue;
  • fragmentación automática incorporada;
  • integración con el marco Luatest;
  • gestión de clústeres utilizando WebUI y API;
  • herramientas de empaque y despliegue.

Hola mundo


No puedo esperar para mostrarle el marco en sí, así que guardemos la historia sobre arquitectura para más adelante, y comencemos con una tarea fácil. Suponiendo que Tarantool ya esté instalado, todo lo que tenemos que hacer es

$ tarantoolctl rocks install cartridge-cli $ export PATH=$PWD/.rocks/bin/:$PATH 

Como resultado, se instalan las utilidades de línea de comandos, lo que le permite crear su primera aplicación desde la plantilla:

 $ cartridge create --name myapp 

Y esto es lo que obtenemos:

 myapp/ ├── .git/ ├── .gitignore ├── app/roles/custom.lua ├── deps.sh ├── init.lua ├── myapp-scm-1.rockspec ├── test │ ├── helper │ │ ├── integration.lua │ │ └── unit.lua │ ├── helper.lua │ ├── integration/api_test.lua │ └── unit/sample_test.lua └── tmp/ 

Este es un repositorio de git con un "Hello, World!" Listo para usar. aplicación Intentemos ejecutarlo después de instalar las dependencias (incluido el propio marco):

 $ tarantoolctl rocks make $ ./init.lua --http-port 8080 

Hemos lanzado un nodo de nuestra futura aplicación fragmentada. Si tiene curiosidad, puede abrir de inmediato la interfaz web, que se ejecuta en localhost : 8080, usar un mouse para configurar un clúster de un nodo y disfrutar del resultado, pero no se entusiasme demasiado pronto. La aplicación aún no sabe cómo hacer nada útil, por lo que le contaré sobre la implementación más adelante, y ahora es el momento de escribir un código.

Desarrollando aplicaciones


Imagine que estamos diseñando un sistema que debería recibir datos, guardarlos y crear un informe una vez al día.


Entonces dibujamos un diagrama con tres componentes: puerta de enlace, almacenamiento y planificador. Sigamos trabajando en la arquitectura. Como usamos vshard como almacenamiento, agreguemos vshard-router y vshard-storage al diagrama. Ni la puerta de enlace ni el planificador accederán directamente al almacenamiento: se crea explícitamente un enrutador para esta tarea.


Este diagrama se ve abstracto porque los componentes aún no reflejan lo que crearemos en el proyecto. Tendremos que ver cómo este proyecto corresponde a Tarantool real, por lo que agrupamos nuestros componentes por el proceso.


No tiene mucho sentido mantener vshard-router y gateway en instancias separadas. ¿Por qué volveríamos a pasar por la red, si esto ya es responsabilidad del enrutador? Deben ejecutarse dentro del mismo proceso, es decir, tanto la puerta de enlace como vshard.router.cfg deben inicializarse en el mismo proceso e interactuar localmente.

Durante la fase de diseño, fue conveniente trabajar con tres componentes, pero como desarrollador, no quiero pensar en lanzar tres instancias de Tarantool mientras escribo código. Necesito ejecutar las pruebas y verificar que escribí el código de la puerta de enlace correctamente. O tal vez quiera mostrar una nueva función a mis compañeros de trabajo. ¿Por qué tendría problemas con el despliegue de tres instancias? Así nació el concepto de roles. Un rol es un módulo Lua normal, y Cartridge gestiona su ciclo de vida. En este ejemplo, hay cuatro de ellos: puerta de enlace, enrutador, almacenamiento y programador. Otro proyecto puede tener más roles. Todos los roles se pueden lanzar en un solo proceso, y sería suficiente.


Y cuando el asunto se refiere a la implementación en la puesta en escena o la producción, asignamos un conjunto separado de roles a cada proceso de Tarantool, dependiendo de las capacidades de hardware subyacentes:


Gestión de topología


También deberíamos almacenar información sobre los roles en ejecución en algún lugar. Y "en algún lugar" significa la configuración distribuida mencionada anteriormente. Lo más importante aquí es la topología de clúster. Aquí puede ver 3 grupos de replicación de 5 procesos de Tarantool:


No queremos perder los datos, por lo que tratamos la información sobre los procesos en ejecución con cuidado. Cartridge supervisa la configuración mediante una confirmación de dos fases. Tan pronto como queramos actualizar la configuración, primero verifica si las instancias están disponibles y listas para aceptar la nueva configuración. Después de eso, la configuración se aplica en la segunda fase. Por lo tanto, incluso si una instancia no está disponible temporalmente, nada puede salir mal. La configuración simplemente no se aplicará y verá un error por adelantado.

La sección de topología también tiene un parámetro tan importante como el líder de cada grupo de replicación. Por lo general, esta es la instancia que acepta las escrituras. El resto suele ser de solo lectura, aunque puede haber excepciones. A veces, los desarrolladores valientes no temen los conflictos y pueden escribir datos en varias réplicas al mismo tiempo. Sin embargo, algunas operaciones no deben realizarse dos veces. Por eso tenemos un líder.


Ciclo de vida del papel


Para que una arquitectura de proyecto contenga roles abstractos, el marco de alguna manera debe poder administrarlos. Naturalmente, los roles se gestionan sin reiniciar el proceso de Tarantool. Hay cuatro devoluciones de llamada diseñadas para la gestión de roles. El propio cartucho los llama según la información de la configuración distribuida, aplicando así la configuración a los roles específicos.

 function init() function validate_config() function apply_config() function stop() 

Cada rol tiene una función init . Se llama una vez: cuando el rol está habilitado o cuando Tarantool se reinicia. Aquí es conveniente, por ejemplo, inicializar box.space.create, o el programador puede ejecutar alguna fibra de fondo que completaría la tarea a intervalos regulares.

La función init sí sola puede no ser suficiente. Cartridge permite que los roles accedan a la configuración distribuida utilizada para almacenar la topología. En la misma configuración, podemos declarar una nueva sección y almacenar una parte de la configuración empresarial allí. En mi ejemplo, esto podría ser un esquema de datos o configuraciones de programación para el rol de planificador.

El clúster llama a validate_config y apply_config cada vez que cambia la configuración distribuida. Cuando se aplica una configuración en una confirmación de dos fases, el clúster verifica que cada rol en cada servidor esté listo para aceptar esta nueva configuración y, si es necesario, informa un error al usuario. Cuando todos están de acuerdo con la configuración, se llama a apply_config .

Los roles también admiten un método de stop para limpiar la basura. Si decimos que no hay necesidad del planificador en este servidor, puede detener las fibras que comenzó a usar init .

Los roles pueden interactuar entre sí. Estamos acostumbrados a escribir llamadas a funciones de Lua, pero el proceso podría no tener el rol necesario. Para facilitar el acceso a la red, utilizamos un módulo auxiliar llamado rpc (llamada a procedimiento remoto), que se basa en el módulo estándar de Tarantool net.box. Esto puede ser útil, por ejemplo, si su puerta de enlace quiere pedirle directamente al programador que realice la tarea ahora, en lugar de hacerlo en un día.

Otro punto importante es garantizar la tolerancia a fallas. Cartridge utiliza el protocolo SWIM [4] para controlar el estado. En resumen, los procesos intercambian "rumores" entre sí a través de UDP, es decir, cada proceso informa a sus vecinos las últimas noticias y ellos responden. Si de repente no hay respuesta, Tarantool sospecha que algo está mal, y después de un tiempo, declara la muerte y envía este mensaje a todos.


Según este protocolo, Cartridge organiza la conmutación por error automática. Cada proceso monitorea su entorno, y si el líder deja de responder repentinamente, la réplica podría reclamar su rol, y Cartridge configuraría los roles en ejecución en consecuencia.


Debe tener cuidado aquí porque el cambio frecuente de ida y vuelta puede generar conflictos de datos durante la replicación. La conmutación por error automática no debe activarse al azar. Debe tener una idea clara de lo que está sucediendo y asegurarse de que la replicación no se bloquee cuando el líder se recupere y recupere su corona.

De todo lo que se ha dicho, los roles pueden parecer similares a los microservicios. En cierto sentido, son pero solo como módulos dentro de los procesos de Tarantool, y hay varias diferencias fundamentales. Primero, todos los roles del proyecto deben vivir en la misma base de código. Y todos los procesos de Tarantool deben ejecutarse desde la misma base de código, para que no haya sorpresas, como cuando intentamos inicializar el programador, pero simplemente no hay programador. Además, no deberíamos permitir diferencias en las versiones de código porque el comportamiento del sistema es complicado de predecir y depurar en tal situación.

A diferencia de Docker, no podemos simplemente tomar una "imagen" de un rol, transferirlo a otra máquina y ejecutarlo allí. Nuestros roles no están tan aislados como los contenedores Docker. Además, no podemos ejecutar dos roles idénticos en la misma instancia. El papel está ahí o no está; en cierto sentido, es un singleton. Y en tercer lugar, los roles deberían ser los mismos en todo el grupo de replicación porque, de lo contrario, se vería ridículo: los datos son los mismos, pero el comportamiento es diferente.

Herramientas de implementación


Prometí mostrarle cómo Cartridge podría ayudar a implementar aplicaciones. Para facilitar la vida, el marco crea paquetes RPM:

 $ cartridge pack rpm myapp # will create ./myapp-0.1.0-1.rpm $ sudo yum install ./myapp-0.1.0-1.rpm 

El paquete instalado contiene casi todo lo que necesita: tanto la aplicación como las dependencias Lua instaladas. Tarantool también llega al servidor como una dependencia del paquete RPM, y nuestro servicio está listo para lanzarse. Todo esto se hace usando systemd, pero primero, debemos hacer alguna configuración, al menos especificar el URI de cada proceso. Tres serían suficientes para nuestro ejemplo.

 $ sudo tee /etc/tarantool/conf.d/demo.yml <<CONFIG myapp.router: {"advertise_uri": "localhost:3301", "http_port": 8080} myapp.storage_A: {"advertise_uri": "localhost:3302", "http_enabled": False} myapp.storage_B: {"advertise_uri": "localhost:3303", "http_enabled": False} CONFIG 

Hay un aspecto interesante que debe considerarse: en lugar de especificar solo el puerto de protocolo binario, especificamos la dirección pública de todo el proceso, incluido el nombre de host. Estamos haciendo esto porque los nodos del clúster deberían saber cómo conectarse entre sí. Sería una mala idea usar la dirección 0.0.0.0 como publicidad_uri, ya que debería ser una dirección IP externa, en lugar de un enlace de socket. Nada funciona sin él, por lo que Cartridge simplemente no permitiría que se inicie el nodo con el publicidad errónea.

Ahora que la configuración está lista, podemos comenzar los procesos. Como una unidad systemd normal no permite iniciar múltiples procesos, las llamadas unidades instanciadas instalan las aplicaciones en Cartridge:

 $ sudo systemctl start myapp@router $ sudo systemctl start myapp@storage_A $ sudo systemctl start myapp@storage_B 

Hemos especificado el puerto HTTP para la interfaz web del cartucho en la configuración: 8080. Veamos allí y echemos un vistazo:


Podemos ver que los procesos aún no están configurados, aunque ya se están ejecutando. Cartridge aún no sabe cómo se debe realizar la replicación y no puede decidir por sí solo, por lo que está esperando nuestras acciones. No tenemos muchas opciones: la vida de un nuevo clúster comienza con la configuración del primer nodo. Luego agregamos otros nodos al clúster, les asignamos roles y la implementación se puede considerar completada con éxito.

Vamos a tomar una copa y relajarnos después de una larga semana laboral. La aplicación está lista para usar.


Resultados


¿Qué hay de los resultados? Pruebe, use, deje comentarios y cree tickets en Github.

Referencias


[1] Tarantool »2.2» Referencia »Referencia de rocas» Módulo vshard
[2] Cómo implementamos el núcleo del negocio de inversiones de Alfa-Bank basado en Tarantool
[3] Arquitectura de facturación de próxima generación: transición a Tarantool
[4] SWIM - Protocolo de creación de clústeres
[5] GitHub - tarantool / cartucho-cli
[6] GitHub - tarantool / cartucho

Source: https://habr.com/ru/post/470812/


All Articles