¿Cuál es la estrategia de transacción más aceptada para microservicios?

80

Uno de los principales problemas que he visto en un sistema con microservicios es la forma en que funcionan las transacciones cuando abarcan diferentes servicios. Dentro de nuestra propia arquitectura, hemos estado utilizando transacciones distribuidas para resolver esto, pero vienen con sus propios problemas. Especialmente los callejones sin salida han sido un dolor hasta ahora.

Otra opción parece ser algún tipo de administrador de transacciones personalizado, que conoce los flujos dentro de su sistema y se encargará de las reversiones por usted como un proceso en segundo plano que abarca todo su sistema (por lo que le dirá a los otros servicios que retrocedan). y si están caídos, notifíquelos más tarde).

¿Hay otra opción aceptada? Ambos parecen tener sus desventajas. El primero podría causar puntos muertos y un montón de otros problemas, el segundo podría resultar en inconsistencia de datos. ¿Hay mejores opciones?

Kristof
fuente
Solo para estar seguro, ¿esos microservicios son utilizados por muchos clientes al mismo tiempo, o solo uno a la vez?
Marcel
2
Estaba tratando de hacer esta pregunta agnóstica a eso, Marcel. Pero supongamos que estamos utilizando un sistema lo suficientemente grande como para hacer ambas cosas, y queremos tener una arquitectura que sea compatible con ambos.
Kristof
44
Esta es una pregunta mal redactada, pero muy importante. Cuando la mayoría de las personas piensan en "transacciones", piensan casi exclusivamente en la consistencia y no en los aspectos de atomicidad, aislamiento o durabilidad de las transacciones. La pregunta en realidad debería decirse "¿Cómo se crea un sistema ácido-compatible dada una arquitectura microservicios Sólo la aplicación de consistencia y no el resto de ácido no es muy útil a menos que te gusta malos datos...
Jeff Fischer
10
Siempre puedes intentar alterar mi pregunta para que sea menos "mal redactada", Jeff Fischer.
Kristof
Esta pregunta y discusión están estrechamente relacionadas con esta otra pregunta sobre SO .
Paulo Merson

Respuestas:

42

El enfoque habitual es aislar esos microservicios tanto como sea posible: tratarlos como unidades individuales. Luego, las transacciones se pueden desarrollar en el contexto del servicio como un todo (es decir, no como parte de las transacciones de DB habituales, aunque aún puede tener transacciones de DB internas al servicio).

Piense cómo ocurren las transacciones y qué tipo tiene sentido para sus servicios, entonces puede implementar un mecanismo de reversión que cancela la operación original, o un sistema de confirmación de 2 fases que reserva la operación original hasta que se le indique que se comprometa de verdad. Por supuesto, ambos sistemas significan que está implementando el suyo, pero ya está implementando sus microservicios.

Los servicios financieros hacen este tipo de cosas todo el tiempo: si quiero mover dinero de mi banco a su banco, no hay una transacción única como la que tendría en un DB. No sabe qué sistemas está ejecutando ninguno de los bancos, por lo que debe tratar a cada uno de manera efectiva como sus microservicios. En este caso, mi banco movería mi dinero de mi cuenta a una cuenta de cartera y luego le diría a su banco que tienen algo de dinero, si ese envío falla, mi banco reembolsará mi cuenta con el dinero que intentaron enviar.

gbjbaanb
fuente
55
Está asumiendo que el reembolso nunca puede fallar.
Sanjeev Kumar Dangi
99
@SanjeevKumarDangi es menos probable e incluso si falla, puede manejarse fácilmente ya que la cuenta de haberes y la cuenta real están bajo el control del banco.
gldraphael
30

Creo que la sabiduría estándar es nunca tener transacciones que crucen los límites del microservicio. Si cualquier conjunto de datos realmente necesita ser atómicamente consistente con otro, esas dos cosas van juntas.

Esta es una de las razones por las que es muy difícil dividir un sistema en servicios hasta que lo haya diseñado completamente. Lo que en el mundo moderno probablemente significa escrito ...

soru
fuente
37
Tal enfoque podría conducir a la fusión de todos los microservicios en una sola aplicación monolítica al final.
Slava Fomin II
44
Este es el enfoque correcto. En realidad es simple: si necesita una transacción entre servicios, sus servicios están equivocados: ¡rediseñelos! @SlavaFominII lo que dices solo es cierto si no sabes cómo diseñar un sistema de microservicio. Si te encuentras luchando contra el enfoque de microservicios, no lo hagas, tu monolito será mejor que un mal diseño de microservicio. Solo cuando encuentre los límites de servicio correctos es cuando debe dividir el monolito en los servicios. De lo contrario, el uso de microservicios no es la opción arquitectónica correcta, solo sigue el bombo publicitario.
Francesc Castells
@FrancescCastells ¿Qué sucede si nuestros servicios realmente requieren transacciones entre otros servicios? ¿Quiere decir que debemos ignorar los contextos limitados y modelar nuestros servicios de manera que terminen como una transacción única? Soy un novato en microservicios, sigo leyendo, así que perdone mi pregunta si suena ingenuo ...: D: D
Bilbo Baggins el
@BilboBaggins Me refiero a lo contrario de ignorar los contextos limitados. Por definición, las transacciones ocurren dentro de un contexto acotado y no en varios de ellos. Por lo tanto, si diseña correctamente sus microservicios junto con sus contextos acotados, sus transacciones no deberían abarcar múltiples servicios. Tenga en cuenta que, en algún momento, lo que necesita no son transacciones, sino un mejor manejo de la consistencia eventual y acciones de compensación adecuadas cuando las cosas no salen correctamente.
Francesc Castells
No entiendo tu punto, ¿hay alguna posibilidad de que podamos discutir esto en un chat?
Bilbo Baggins
17

Creo que si la coherencia es un requisito importante en su aplicación, debe preguntarse si el mejor enfoque es microservicios. Como dice Martin Fowler :

Los microservicios introducen eventuales problemas de consistencia debido a su loable insistencia en la gestión descentralizada de datos. Con un monolito, puede actualizar un montón de cosas juntas en una sola transacción. Los microservicios requieren múltiples recursos para actualizarse, y las transacciones distribuidas están mal vistas (por una buena razón). Entonces, los desarrolladores deben ser conscientes de los problemas de coherencia y descubrir cómo detectar cuándo las cosas no están sincronizadas antes de hacer algo de lo que el código se arrepentirá.

Pero tal vez en su caso, puede sacrificar la consistencia en pos de disponibilidad

Los procesos comerciales a menudo son más tolerantes a las inconsistencias de lo que piensas porque las empresas a menudo valoran más la disponibilidad.

Sin embargo, también me pregunto si existe una estrategia para transacciones distribuidas en microservicios, pero tal vez el costo es demasiado alto. Quería darle mis dos centavos con el siempre excelente artículo de Martin Fowler y el teorema CAP .

gabrielgiussi
fuente
1
En las transacciones comerciales distribuidas, la coherencia a veces se aborda agregando una capa de orquestación como un motor de flujo de trabajo o una cola cuidadosamente diseñada. Esa capa maneja todas las dos fases de confirmación y retroceso, y permite que los microservicios se centren en la lógica empresarial específica. Pero volviendo a CAP, invertir en disponibilidad y consistencia hace que el desempeño sea la víctima. ¿Cómo se comparan los microservicios con muchas clases desacopladas que comprenden su negocio en OOP?
Greg
Puede usar RAFT a través de microservicios para abordar la coherencia FWIW
f0ster
1
+1. A menudo me pregunto por qué en un contexto de microservicios se desean transacciones, si todas las vistas 'pull' de los datos se pueden implementar como vistas materializadas. Por ejemplo, si un microservicio implementa débitos de una cuenta y otros créditos a otra cuenta, las vistas de saldos solo considerarían pares de créditos y débitos, donde los créditos y débitos sin igual aún estarían en los buffers de la vista materializada.
Sentinel
16

Como se sugiere en al menos una de las respuestas aquí, pero también en otras partes de la web, es posible diseñar un microservicio que persista a las entidades juntas dentro de una transacción normal si necesita coherencia entre las dos entidades.

Pero al mismo tiempo, bien podría tener la situación en la que las entidades realmente no pertenecen al mismo microservicio, por ejemplo, registros de ventas y registros de pedidos (cuando ordena algo para completar la venta). En tales casos, es posible que necesite una forma de garantizar la coherencia entre los dos microservicios.

Las transacciones tradicionalmente distribuidas se han utilizado y, en mi experiencia, funcionan bien hasta que se escalan a un tamaño en el que el bloqueo se convierte en un problema. Puede relajar el bloqueo para que realmente solo los recursos relevantes (por ejemplo, el artículo que se vende) se "bloqueen" utilizando un cambio de estado, pero aquí es donde comienza a complicarse porque está entrando en el territorio donde necesita construir todo lógica para hacer esto, usted mismo, en lugar de tener, digamos que una base de datos lo maneja por usted.

He trabajado con compañías que han tomado la ruta de construir su propio marco de transacciones para manejar este problema complejo, pero no lo recomiendo porque es costoso y lleva tiempo madurar.

Existen productos que se pueden atornillar a su sistema que se encargan de la consistencia. Un motor de procesos de negocio es un buen ejemplo y generalmente manejan la consistencia eventualmente y mediante el uso de compensación. Otros productos funcionan de manera similar. Por lo general, termina con una capa de software cerca de los clientes, que se ocupa de la coherencia y las transacciones y llama a los (micro) servicios para realizar el procesamiento comercial real . Uno de esos productos es un conector JCA genérico que se puede usar con soluciones Java EE (por transparencia: soy el autor). Consulte http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html para obtener más detalles y una discusión más profunda de los problemas planteados aquí.

Otra forma de manejar las transacciones y la coherencia es envolver una llamada a un microservicio en una llamada a algo transaccional como una cola de mensajes. Tome el ejemplo de registro de ventas / registro de pedidos de arriba: simplemente puede dejar que el microservicio de ventas envíe un mensaje al sistema de pedidos, que se confirma en la misma transacción que escribe la venta en la base de datos. El resultado es una solución asincrónica que escala muy bien. Utilizando tecnologías como los sockets web, incluso puede evitar el problema del bloqueo, que a menudo está relacionado con la ampliación de soluciones asincrónicas. Para obtener más ideas sobre patrones como este, consulte otro de mis artículos: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

Sea cual sea la solución que elija, es importante reconocer que solo una pequeña parte de su sistema escribirá cosas que deben ser consistentes; es probable que la mayoría del acceso sea de solo lectura. Por esa razón, cree la administración de transacciones solo en las partes relevantes del sistema, para que aún pueda escalar bien.

Hormiga Kutschera
fuente
Mientras tanto, diría que uno debería considerar seriamente convertir el proceso en asíncrono, donde cada paso del proceso es completamente transaccional. Ver aquí para más detalles: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Ant Kutschera
"Haga el ejemplo de registro de ventas / registro de pedidos desde arriba: simplemente puede dejar que el microservicio de ventas envíe un mensaje al sistema de pedidos, que se confirma en la misma transacción que escribe la venta en la base de datos". No estoy seguro si lo que está sugiriendo es básicamente una transacción distribuida, dicho eso, ¿cómo manejaría el escenario de reversión en este caso? Por ejemplo, el mensaje se compromete con la cola de mensajes pero se revierte en el lado de la base de datos.
Sábado
@sactiw no está seguro de si podría haber tenido en cuenta la confirmación de dos fases, pero lo evitaría ahora y en su lugar escribiría mis datos comerciales, y el hecho de que un mensaje debe agregarse a la cola, en una transacción a mi base de datos de microservicio . El "hecho", también conocido como "comando", se procesa de forma asíncrona después de que se confirma la transacción, utilizando un mecanismo de reintento automatizado. Vea el artículo del blog del 10-03-2018 para ver un ejemplo. Evite la reversión o la compensación a favor de una estrategia de avance si es posible, ya que es más fácil de implementar.
Ant Kutschera
1

Hay muchas soluciones que comprometen más de lo que me siento cómodo. De acuerdo, si su caso de uso es complejo, como mover dinero entre diferentes bancos, alternativas más agradables pueden ser imposibles. Pero echemos un vistazo a lo que podemos hacer en el escenario común, donde el uso de microservicios interfiere con nuestras posibles transacciones de bases de datos.

Opción 1: evite la necesidad de transacciones si es posible

Obvio y mencionado antes, pero ideal si podemos manejarlo. ¿Los componentes pertenecían realmente al mismo microservicio? ¿O podemos rediseñar los sistemas de manera que la transacción se vuelva innecesaria? Quizás aceptar la no transaccionalidad es el sacrificio más asequible.

Opción 2: usar una cola

Si hay suficiente certeza de que el otro servicio tendrá éxito en lo que queramos que hagamos, podemos llamarlo a través de algún tipo de cola. El artículo en cola no se recogerá hasta más tarde, pero podemos asegurarnos de que esté en cola .

Por ejemplo, supongamos que queremos insertar una entidad y enviar un correo electrónico, como una sola transacción. En lugar de llamar al servidor de correo, colocamos el correo en una tabla.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Un inconveniente claro es que múltiples microservicios necesitarán acceso a la misma tabla.

Opción 3: hacer el trabajo externo al final, justo antes de completar la transacción

Este enfoque se basa en el supuesto de que es muy improbable que la transacción se confirme.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Si las consultas fallan, la llamada externa aún no se ha realizado. Si la llamada externa falla, la transacción nunca se confirma.

Este enfoque viene con las limitaciones de que solo podemos hacer una llamada externa, y debe hacerse en último lugar (es decir, no podemos usar su resultado en nuestras consultas).

Opción 4: crear cosas en estado pendiente

Como se publicó aquí , podemos hacer que múltiples microservicios creen diferentes componentes, cada uno en un estado pendiente, no transaccional.

Se realiza cualquier validación, pero nada se crea en un estado definitivo. Después de que todo se haya creado con éxito, cada componente se activa. Por lo general, esta operación es tan simple y las probabilidades de que algo salga mal son tan pequeñas que incluso podemos preferir hacer la activación de manera no transaccional.

El mayor inconveniente es probablemente que tenemos que tener en cuenta la existencia de elementos pendientes. Cualquier consulta de selección debe considerar si incluir datos pendientes. La mayoría debería ignorarlo. Y las actualizaciones son otra historia por completo.

Opción 5: dejar que el microservicio comparta su consulta

¿Ninguna de las otras opciones lo hace por ti? Entonces seamos poco ortodoxos .

Dependiendo de la compañía, esta puede ser inaceptable. Soy consciente. Esto no es ortodoxo. Si no es aceptable, toma otra ruta. Pero si esto se ajusta a su situación, resuelve el problema de manera simple y poderosa. Podría ser el compromiso más aceptable.

Hay una manera de convertir las consultas de múltiples microservicios en una transacción de base de datos simple y única.

Devuelve la consulta, en lugar de ejecutarla.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

En cuanto a la red, cada microservicio debe poder acceder a cada base de datos. Tenga esto en cuenta, también con respecto a la escala futura.

Si las bases de datos involucradas en la transacción están en el mismo servidor, esta será una transacción regular. Si están en servidores diferentes, será una transacción distribuida. El código es el mismo independientemente.

Recibimos la consulta, incluido su tipo de conexión, sus parámetros y su cadena de conexión. Podemos envolverlo en una clase de comando ejecutable ordenada, manteniendo el flujo legible: la llamada al microservicio da como resultado un comando, que ejecutamos, como parte de nuestra transacción.

La cadena de conexión es lo que nos proporciona el microservicio de origen, por lo que, para todos los efectos, el microservicio todavía considera que la consulta se ejecuta. Simplemente lo estamos enrutando físicamente a través del microservicio del cliente. Eso hace una diferencia? Bueno, nos permite ponerlo en la misma transacción con otra consulta.

Si el compromiso es aceptable, este enfoque nos brinda la transaccionalidad directa de una aplicación monolítica, en una arquitectura de microservicio.

Timo
fuente
0

Comenzaría con la descomposición del espacio problemático, identificando los límites de su servicio . Cuando se hace correctamente, nunca necesitaría tener transacciones entre servicios.

Los diferentes servicios tienen sus propios datos, comportamiento, fuerzas motivadoras, gobierno, reglas comerciales, etc. Un buen comienzo es enumerar qué capacidades de alto nivel tiene su empresa. Por ejemplo, marketing, ventas, contabilidad, soporte. Otro punto de partida es la estructura organizativa, pero tenga en cuenta que existe una advertencia: por alguna razón (política, por ejemplo), podría no ser el esquema óptimo de descomposición empresarial. Un enfoque más estricto es el análisis de la cadena de valor . Recuerde, sus servicios también pueden incluir personas, no es estrictamente software. Los servicios deben comunicarse entre sí a través de eventos .

El siguiente paso es tallar estos servicios. Como resultado, aún obtienes agregados relativamente independientes . Representan una unidad de consistencia. En otras palabras, sus componentes internos deben ser consistentes y ACID. Los agregados se comunican entre sí a través de eventos.

Si cree que su dominio exige coherencia primero, piense de nuevo. Ninguno de los sistemas grandes y de misión crítica está construido con esto en mente. Todos se distribuyen y eventualmente son consistentes. Mira el clásico artículo de Pat Helland .

Aquí hay algunos consejos prácticos sobre cómo construir un sistema distribuido.

Zapadlo
fuente