Procesar transacciones distribuidas en una arquitectura de microservicio

¡Hola, Habr!



Hoy traemos a su atención un pequeño material sobre microservicios y arquitectura distribuida. En particular, toca la idea de Martin Fowler de que un nuevo sistema debe comenzar con un monolito, e incluso en una arquitectura de microservicio desarrollada es aconsejable dejar un gran núcleo monolítico.



¡Disfruta leyendo!



Hoy en día, todo el mundo piensa y escribe sobre microservicios, y yo no soy una excepción. Basado en los principios básicos de los microservicios y su verdadero contexto, está claro que los microservicios son un sistema distribuido.



¿Qué es una transacción distribuida?



Las transacciones que abarcan varios sistemas físicos o computadoras en una red se denominan simplemente transacciones distribuidas. En el mundo de los microservicios, una transacción se divide en varios servicios que se llaman en una secuencia para completar la transacción completa.



Aquí hay un sistema de tienda en línea monolítico que utiliza transacciones:







Fig. 1: Una transacción en el monolito



Si en el sistema anterior, un usuario envía una solicitud a la plataforma en el pedido (Checkout), la plataforma crea una transacción local en la base de datos, y esta transacción cubre una multitud de tablas de la base de datos para procesar (Procesar) y el libro de pedidos(Reserva) mercancías del almacén. Si alguno de estos pasos falla, entonces la transacción se puede revertir , lo que significa el rechazo tanto del pedido como de los bienes reservados. Este conjunto de principios se denomina ACID (Atomicidad, Consistencia, Aislamiento, Durabilidad) y está garantizado a nivel del sistema de base de datos.



Aquí hay una descomposición de un sistema de tienda en línea construido a partir de microservicios:







Figura 2: Transacciones en un microservicio



Después de descomponer este sistema, creamos microservicios OrderMicroserviceyInventoryMicroservicecon bases de datos independientes. Cuando una solicitud de Checkout proviene de un usuario, se llaman a ambos microservicios y cada uno de ellos realiza cambios en su base de datos. Dado que una transacción ahora se propaga a través de múltiples bases de datos en múltiples sistemas, se considera distribuida .



¿Cuál es el problema al realizar transacciones distribuidas en microservicios?



Con la introducción de la arquitectura de microservicios, las bases de datos están perdiendo su naturaleza ACID. Debido a la posible proliferación de transacciones entre muchos microservicios y, por tanto, bases de datos, uno tiene que lidiar con los siguientes problemas clave:



¿Cómo mantener la atomicidad de las transacciones?



Atomicidad significa que en cualquier transacción, se pueden completar todos los pasos o ninguno. Si el ejemplo anterior no completa la operación de 'pedidos de artículos' en el método InventoryMicroservice, ¿cómo revertir los cambios en el 'procesamiento de pedidos' que se aplicaron OrderMicroservice?



¿Cómo manejo las solicitudes competitivas?



Supongamos que un objeto de cualquiera de los microservicios ingresa a la base de datos para su almacenamiento a largo plazo y, al mismo tiempo, otra solicitud lee el mismo objeto. ¿Qué datos debe devolver el servicio, antiguos o nuevos? En el ejemplo anterior, cuando ya OrderMicroserviceha finalizado el trabajo y InventoryMicroserviceestá en proceso de actualización, ¿es necesario incluir el pedido actual en el número de solicitudes de pedidos realizados por el usuario?



Los sistemas modernos están diseñados teniendo en cuenta las posibles fallas y uno de los principales problemas del procesamiento de transacciones distribuidas está bien articulado por Pat Helland.

Como regla general, los desarrolladores simplemente no crean grandes aplicaciones escalables que involucren trabajar con transacciones distribuidas.



Soluciones posibles



Los dos problemas anteriores son muy críticos en el contexto del diseño y la creación de aplicaciones basadas en microservicios. Para resolverlos, se utilizan los siguientes dos enfoques:



  • Fijación bifásica
  • Máxima consistencia y compensación / SAGA


1. Fijación en dos fases



Como su nombre lo indica, este método de procesamiento de transacciones implica dos etapas: una fase de preparación y una fase de confirmación. En este caso, el coordinador de transacciones desempeña un papel importante, organizando el ciclo de vida de la transacción.



Cómo funciona



En la etapa preparatoria, todos los microservicios que participan en el trabajo se preparan para el compromiso y notifican al coordinador que están listos para completar la transacción. Luego, en el siguiente paso, se produce una confirmación o el coordinador de transacciones emite un comando a todos los microservicios para que retrocedan.



Considere nuevamente un sistema de tienda en línea como ejemplo:







Figura 3: Confirmación exitosa de dos fases en un sistema de microservicio



En el ejemplo anterior (Figura 3), cuando un usuario envía una solicitud de pedido, el coordinador TransactionCoordinatorprimero inicia una transacción global con información de contexto completa. Primero, envía el comando de preparación al microservicio OrderMicroservicepara crear el pedido. Luego envía el comando de preparación aInventoryMicroservicepara reservar artículos. Cuando ambos servicios están listos para realizar cambios, bloquean los objetos de cambios adicionales y notifican al respecto TransactionCoordinator. Una vez que TransactionCoordinatorconfirme que todos los microservicios están listos para aplicar sus cambios, ordenará estos microservicios para guardarlos solicitando una confirmación de la transacción. En este punto, todos los objetos se desbloquearán.





Figura 4: Confirmación fallida en dos fases al trabajar con microservicios



En un escenario de falla (Figura 4): si en algún momento un solo microservicio no tiene tiempo para prepararse, TransactionCoordinatorcancele la transacción y comience el proceso de reversión. En el diagrama, OrderMicroservicepor alguna razón, no pude crear un pedido, pero InventoryMicroservicerespondí que estaba listo para crear un pedido. El coordinador TransactionCoordinatorsolicitará una cancelación enInventoryMicroservice, después de lo cual el servicio revertirá todos los cambios realizados y desbloqueará los objetos de la base de datos.



Beneficios



  • Este enfoque garantiza la atomicidad de la transacción. La transacción se completará cuando ambos microservicios tengan éxito o cuando los microservicios no realicen ningún cambio.
  • En segundo lugar, este enfoque le permite aislar la lectura de la escritura, ya que los cambios en los objetos no son visibles hasta que el coordinador de transacciones confirma estos cambios.
  • Este enfoque es una llamada sincrónica en la que se notificará al cliente sobre el éxito o el fracaso.


desventajas



  • Nada es perfecto; Las confirmaciones de dos fases son bastante lentas en comparación con las operaciones de un solo microservicio. Dependen mucho del coordinador. transacciones, que pueden ralentizar significativamente el sistema durante períodos de alta carga.
  • Otro gran inconveniente es el bloqueo de filas de la base de datos. El bloqueo puede convertirse en un cuello de botella en el rendimiento y pueden producirse puntos muertos , donde dos transacciones se bloquean estrechamente entre sí.


2. Máxima coherencia y compensación / SAGA



Una de las mejores definiciones de coherencia se da en última instancia en microservices.io: cada servicio publica un evento cada vez que se actualizan sus datos. Otros servicios se suscriben a eventos. Cuando se recibe un evento, el servicio actualiza sus datos .



Con este enfoque, una transacción distribuida se ejecuta como una colección de transacciones locales asincrónicas en los microservicios correspondientes. Los microservicios intercambian información a través del bus de eventos.



Cómo funciona



De nuevo, tomemos un ejemplo de un sistema que se ejecuta en una tienda en línea:







Figura 5: Consistencia máxima / SAGA, éxito



En el ejemplo anterior (Figura 5), ​​el cliente requiere que el sistema procese el pedido. Esta solicitud Choreographergenera el evento Create Order, que inicia la transacción. El microservicio OrderMicroserviceescucha este evento y crea un pedido; si esta operación se realiza correctamente, genera el evento Pedido creado. El coordinador Choreographerescucha este evento y procede a pedir artículos, lo que genera el evento Reservar artículos. MicroservicioInventoryMicroserviceescucha este evento y ordena mercancías; si este evento tiene éxito, genera el evento Artículos reservados. En este ejemplo, esto significa que la transacción ha finalizado.



Toda la comunicación basada en eventos entre microservicios ocurre a través del bus de eventos, y otro sistema es responsable de su organización (coreografía); así es como se resuelve el problema con una complejidad innecesaria.







Figura 6: Consistencia máxima / SAGA, resultado fallido



Si, por alguna razón, los InventoryMicroserviceelementos no se reservaron (Figura 6), se genera el evento Falló al reservar elementos. El coordinador Choreographerescucha este evento e inicia la transacción de compensación, generando el evento Delete Order. MicroservicioOrderMicroservice escucha este evento y elimina el orden creado anteriormente.



Beneficios



Una de las principales ventajas de este enfoque es que cada microservicio se centra solo en su propia transacción atómica. Los microservicios no se bloquean si otro servicio tarda un tiempo relativamente largo en ejecutarse. Esto también significa que tampoco es necesario bloquear la base de datos. Con este enfoque, es posible asegurar una buena escalabilidad del sistema cuando se trabaja con cargas elevadas, ya que la solución propuesta es asíncrona y se basa en trabajar con eventos.



desventajas



La principal desventaja de este enfoque es que no proporciona aislamiento de lectura. Por lo tanto, en el ejemplo anterior, el cliente verá que se ha creado el pedido, pero después de un segundo, el pedido se eliminará durante la transacción de compensación. Además, a medida que aumenta la cantidad de microservicios, se vuelven más difíciles de depurar y mantener.



Conclusión



La primera alternativa al enfoque propuesto es abandonar por completo las transacciones distribuidas. Si está creando una nueva aplicación, comience con una arquitectura monolítica, como se describe en MonolithFirst por Martin Fowler. Lo citaré.

, , . , , . —
Si es necesario actualizar los datos en dos lugares a la vez como resultado de un solo evento, entonces se prefiere el enfoque de consistencia / SAGA en última instancia al enfoque de dos fases para procesar transacciones distribuidas. La razón principal es que el enfoque de dos fases en un entorno distribuido no escala. El uso de la coherencia también plantea eventualmente su propio conjunto de problemas, como cómo actualizar atómicamente la base de datos y activar un evento. Pasando a tal filosofía de desarrollo, es necesario cambiar su percepción tanto desde el punto de vista del desarrollador como del tester.



All Articles