Microservicios (2)

1. El
principio CAP CAP, también conocido como el teorema CAP, se refiere a la consistencia, disponibilidad y tolerancia de partición en un sistema distribuido.

一致性(C): La coherencia se refiere a "todos los nodos ven los mismos datos al mismo tiempo", es decir, después de que la operación de actualización es exitosa y regresa al cliente, los datos de todos los nodos al mismo tiempo son completamente consistentes. (Consistencia fuerte)
可用性(A): la disponibilidad se refiere a "Las lecturas y escrituras siempre tienen éxito", es decir, el servicio siempre está disponible y el tiempo de respuesta normal. (Alta disponibilidad)
分区容忍性(P): el sistema continúa funcionando a pesar de la pérdida arbitraria de mensajes o falla de parte del sistema ". El sistema continúa operando a pesar de la pérdida o falla de cualquier mensaje en algunos sistemas.

取舍策略:
CA sin P: si no se requiere P (no se permite la partición), se puede garantizar C (consistencia fuerte) y A (disponibilidad). Pero renunciar a P al mismo tiempo significa renunciar a la escalabilidad del sistema, es decir, los nodos distribuidos son limitados y no hay forma de desplegar nodos secundarios, lo que es contrario a la intención original del diseño del sistema distribuido.

CP sin A: si no se requiere A (disponible), significa que cada solicitud debe ser muy consistente entre los servidores, y P (partición) hará que el tiempo de sincronización se extienda infinitamente (es decir, espere a que la sincronización de datos complete el acceso normal al servicio) Una vez que ocurre una falla en la red o una pérdida de mensaje, debe sacrificar la experiencia del usuario y esperar a que todos los datos sean consistentes antes de permitir que los usuarios accedan al sistema. En realidad, hay muchos sistemas diseñados como CP. Los más típicos son las bases de datos distribuidas como Redis y HBase. Para estas bases de datos distribuidas, la consistencia de los datos es el requisito más básico, porque incluso si este estándar no se cumple, entonces es bueno usar directamente una base de datos relacional, y no hay necesidad de desperdiciar recursos para implementar bases de datos distribuidas.

AP sin C: para estar altamente disponible y permitir la partición, debe renunciar a la coherencia. Una vez que se produce la partición, los nodos pueden perder el contacto. Para una alta disponibilidad, cada nodo solo puede usar datos locales para proporcionar servicios, y esto generará inconsistencias en los datos globales. Una aplicación típica es como un escenario de teléfono móvil de compra apresurada de un determinado medidor. Es posible que se le solicite que haga un inventario cuando explore el producto hace unos segundos. Cuando haya seleccionado el producto y esté listo para realizar un pedido, el sistema le indicará que el pedido ha fallado y el producto se ha vendido. . Esto es para garantizar el servicio normal del sistema en A (disponibilidad) y luego hacer algunos sacrificios en la consistencia de los datos. Aunque afectará la experiencia del usuario, no causará un bloqueo serio del proceso de compra del usuario.

BASE理论:
BASE es la abreviatura de las tres frases Básicamente disponible, estado suave y eventualmente consistente. La teoría BASE es el resultado del equilibrio entre consistencia y disponibilidad en CAP. Proviene de un resumen de la práctica distribuida de los sistemas de Internet a gran escala y se desarrolla gradualmente según el teorema CAP. La idea central de la teoría BASE es: incluso si no se puede lograr una fuerte consistencia, cada aplicación puede usar métodos apropiados para lograr la consistencia final del sistema de acuerdo con sus propias características comerciales. A continuación, observe los tres elementos en BASE:

1. Disponibilidad
básica La disponibilidad básica significa que el sistema distribuido puede perder parte de su disponibilidad cuando ocurre una falla impredecible; tenga en cuenta que esto no es equivalente a que el sistema no esté disponible. Por ejemplo:
(1) Pérdida en el tiempo de respuesta. En circunstancias normales, un motor de búsqueda en línea necesita devolver los resultados de la consulta correspondiente al usuario dentro de 0,5 segundos, pero debido a una falla, el tiempo de respuesta de los resultados de la consulta aumenta de 1 a 2 segundos.
(2) Pérdida de las funciones del sistema: en circunstancias normales, al comprar en un sitio web de comercio electrónico, los consumidores pueden completar casi cada pedido sin problemas, pero en algunos festivales pico cuando compran pico Para proteger la estabilidad del sistema de compras, algunos consumidores pueden ser dirigidos a una página degradada para proteger la estabilidad del sistema de compras.

2. Estado
suave El estado suave significa que se permite que los datos en el sistema existan en un estado intermedio, y la existencia del estado intermedio no afecta la disponibilidad general del sistema, es decir, hay un retraso en el proceso de permitir que el sistema sincronice datos entre copias de datos de diferentes nodos .

3. Consistencia final La consistencia
final enfatiza que todas las copias de datos, después de un período de sincronización, eventualmente pueden alcanzar un estado consistente. Por lo tanto, la esencia de la consistencia final es que el sistema necesita garantizar que los datos finales puedan alcanzar la consistencia, pero no necesita garantizar la consistencia sólida de los datos del sistema en tiempo real.

En general, la teoría BASE está orientada a sistemas distribuidos escalables y de alta disponibilidad a gran escala, lo cual es contrario a las características tradicionales de ACID. Es completamente diferente del modelo de fuerte consistencia de ACID, pero se obtiene sacrificando una fuerte consistencia. Disponibilidad y permitir que los datos sean inconsistentes por un período de tiempo, pero eventualmente alcancen un estado consistente. Pero al mismo tiempo, en escenarios distribuidos reales, las diferentes unidades de negocios y componentes tienen diferentes requisitos para la consistencia de los datos, por lo tanto, en el proceso de diseño de una arquitectura de sistema distribuido específica, las características de ACID y la teoría BASE a menudo se combinan juntas.

En segundo lugar, la transacción distribuida
de dibujos animados de transacciones distribuidas

什么是分布式事务?
Las transacciones distribuidas se utilizan para garantizar la coherencia de los datos entre diferentes nodos en un sistema distribuido. Existen muchas implementaciones de transacciones distribuidas, la más representativa de las cuales es el protocolo de transacciones distribuidas XA propuesto por el sistema Oracle Tuxedo.

XA协议Contiene dos implementaciones de presentación en dos fases (2PC) y presentación en tres fases (3PC), aquí nos centramos en el proceso específico de presentación en dos fases.

两阶段提交(2PC):
Hay dos roles en el protocolo XA: coordinador de transacciones y participante de la transacción. Echemos un vistazo al proceso de interacción entre ellos:
Fase 1:
Inserte la descripción de la imagen aquí
en la primera fase de las transacciones distribuidas XA, el nodo que actúa como coordinador de la transacción enviará primero las solicitudes de preparación a todos los nodos participantes.

Después de recibir la solicitud de preparación, cada nodo participante realizará una actualización de datos relacionada con la transacción y la escribirá en Deshacer registro y Rehacer registro. Si el participante se ejecuta con éxito, no envía la transacción temporalmente, sino que devuelve un mensaje "completo" al nodo de coordinación de la transacción.

Cuando el coordinador de transacciones recibe los mensajes de devolución de todos los participantes, toda la transacción distribuida entrará en la segunda etapa.

La segunda etapa:
Inserte la descripción de la imagen aquí
en la segunda etapa de las transacciones distribuidas XA, si el nodo de coordinación de la transacción ha recibido un retorno positivo antes, emitirá una solicitud de confirmación a todos los participantes de la transacción.

Después de recibir la solicitud de confirmación, los nodos participantes de la transacción confirmarán cada transacción local y liberarán los recursos de bloqueo. Cuando la transacción local completa la confirmación, devolverá un mensaje "completo" al coordinador de la transacción.

Cuando el coordinador de transacciones recibe comentarios de "finalización" de todos los participantes de la transacción, se completa toda la transacción distribuida.

失败情况: Lo
anterior describe el proceso de envío de dos fases XA. A continuación, echemos un vistazo al proceso de falla:

La primera etapa: la
Inserte la descripción de la imagen aquí
segunda etapa:
Inserte la descripción de la imagen aquí
en la primera etapa de XA, si un participante de la transacción informa un mensaje de error, significa que la transacción local del nodo no es exitosa y debe revertirse.

Entonces, en la segunda fase, el nodo de coordinación de transacciones envía solicitudes de cancelación a todos los participantes de la transacción. Después de recibir la solicitud de cancelación, cada nodo participante de la transacción debe realizar la operación de reversión de la transacción localmente, y la operación de reversión se realiza de acuerdo con el registro de deshacer.

XA两阶段提交的不足:
1. Problemas de rendimiento El
protocolo XA sigue una fuerte consistencia. En el proceso de ejecución de la transacción, cada nodo ocupa los recursos de la base de datos. Solo cuando todos los nodos estén listos, el coordinador de la transacción notificará el envío y los participantes liberarán los recursos después del envío. Tal proceso tiene problemas de rendimiento muy obvios.

2. El problema del punto único de falla del
coordinador El coordinador de la transacción es el núcleo de todo el modelo XA. Una vez que el nodo del coordinador de la transacción cuelga, el participante no puede recibir una notificación de confirmación o reversión, y el participante estará en un estado intermedio y no podrá completar la transacción.

3. Inconsistencias causadas por mensajes faltantes.
En la segunda etapa del protocolo XA, si ocurre un problema de red local, algunos participantes de la transacción reciben un mensaje de confirmación, y otra parte de los participantes de la transacción no recibe un mensaje de confirmación, lo que resulta en datos inconsistentes entre los nodos.

解决二阶段不足方案:
¿Cómo evitar los diversos problemas presentados por XA en dos etapas? Hay muchas otras opciones de transacciones distribuidas para elegir:

1. Compromiso trifásico
XA El compromiso trifásico XA agrega la fase CanCommit en base al compromiso de dos fases e introduce un mecanismo de tiempo de espera. Una vez que el participante no ha recibido la solicitud de confirmación del coordinador, se comprometerá automáticamente a nivel local. Esto resuelve efectivamente el problema del único punto de falla del coordinador. Pero los problemas de rendimiento y las inconsistencias aún no se resuelven fundamentalmente.

2. Transacción MQ
Use el middleware de mensajes para completar asincrónicamente la segunda mitad de la actualización de la transacción para lograr la consistencia final del sistema. Este enfoque evita problemas de rendimiento como el protocolo XA.

3. Transacción
TCC La transacción TCC es la abreviatura de Try, Commit, Cancel, tres instrucciones, su modelo lógico es similar al XA commit de dos fases, pero el método de implementación se implementa artificialmente a nivel de código.

Tercero, bloqueo
distribuido explicación distribuida de cómic

什么是分布式锁?
En un sistema distribuido, se implementa el acceso sincrónico a código y recursos entre diferentes hilos de diferentes procesos.

分布式锁的实现方式:
1. Bloqueo distribuido de
Memcached Use el comando de agregar Memcached. Este comando es una operación atómica. Solo cuando la clave no existe, la adición puede tener éxito, lo que significa que el hilo ha obtenido el bloqueo.

2. El bloqueo distribuido de Redis
es similar a Memcached, utilizando el comando setnx de Redis. Este comando también es una operación atómica. Solo cuando la clave no existe, se puede configurar con éxito. (El comando setnx no es perfecto, y las alternativas se presentarán más adelante)

3. Bloqueo distribuido de
Zookeeper Utilice los nodos temporales secuenciales de Zookeeper para implementar bloqueos distribuidos y colas de espera. La intención original de Zookeeper fue diseñada para realizar el servicio de bloqueo distribuido.

4.
El servicio de bloqueo distribuido de grano grueso implementado por Chubby Google utiliza el algoritmo de consenso de Paxos en la parte inferior.

如何用Redis实现分布式锁?
El proceso básico de los bloqueos distribuidos de Redis no es difícil de entender, pero no es tan fácil escribir lo mejor. Aquí, primero debemos comprender los tres elementos principales de la implementación de bloqueo distribuido:

1. Bloqueo
El método más simple es usar el comando setnx. La clave es el identificador único de la cerradura y se nombra de acuerdo con el negocio. Por ejemplo, si desea bloquear la actividad de picos de una mercancía, puede nombrar la clave "lock_sale_commodity ID". ¿Y cuál es el valor establecido? Podemos configurarlo temporalmente en 1. El pseudocódigo para el bloqueo es el siguiente:

setnx(key,1

Cuando un subproceso ejecuta setnx y devuelve 1, significa que la clave originalmente no existía, y el subproceso obtuvo el bloqueo con éxito; cuando un subproceso ejecuta setnx y devuelve 0, significa que la clave ya existe y el subproceso no logra agarrar el bloqueo.

2. Desbloqueo
Si tiene un bloqueo, debe desbloquearlo. Cuando el hilo que ha obtenido el bloqueo finaliza la tarea, el bloqueo debe liberarse para que puedan ingresar otros hilos. La forma más fácil de liberar el bloqueo es ejecutar la instrucción del, el pseudocódigo es el siguiente:

del(key)

Después de liberar el bloqueo, otros subprocesos pueden continuar ejecutando el comando setnx para obtener el bloqueo.

3. Tiempo de espera de bloqueo
¿Qué significa tiempo de espera de bloqueo? Si un subproceso que adquiere un bloqueo se bloquea durante la ejecución de una tarea y no tiene tiempo para liberarlo explícitamente, este recurso se bloqueará para siempre y otros subprocesos nunca querrán volver a entrar.
Por lo tanto, la clave de setnx debe establecer un período de tiempo de espera para garantizar que, incluso si no se libera explícitamente, el bloqueo debe liberarse automáticamente después de un cierto tiempo. Setnx no admite parámetros de tiempo de espera, por lo que se requieren instrucciones adicionales. El pseudocódigo es el siguiente:

expire(key, 30

En conjunto, la primera versión del pseudocódigo para nuestra implementación de bloqueo distribuido es la siguiente:

if(setnx(key,1== 1{

    expire(key,30)

    try {

        do something ......

    } finally {

        del(key)

    }

}

三个致命问题

  1. La naturaleza no atómica de setnx y caducar
    Imagine un escenario extremo, cuando un hilo ejecuta setnx y obtiene el bloqueo con éxito:
    Inserte la descripción de la imagen aquí
    setnx acaba de ejecutarse con éxito y tiene tiempo de ejecutar la instrucción de caducidad en el futuro.
    Inserte la descripción de la imagen aquí
    De esta manera, el bloqueo no tiene tiempo de caducidad y se vuelve "eterno", y otros hilos ya no pueden obtener el bloqueo.
    ¿Cómo solucionarlo? La instrucción setnx en sí misma no admite el tiempo de espera entrante. Afortunadamente, Redis 2.6.12 o posterior agrega parámetros opcionales a la instrucción set. El pseudocódigo es el siguiente:
set(key,130,NX)

Esto puede reemplazar la instrucción setnx.

  1. del conduce a la eliminación accidental.
    Es otro escenario extremo. Si un subproceso obtiene un bloqueo con éxito y el período de tiempo de espera se establece en 30 segundos.
    Inserte la descripción de la imagen aquí
    Si por alguna razón el subproceso B se ejecuta muy lentamente, no terminará después de 30 segundos. En este momento, el bloqueo expira y se libera automáticamente, y el hilo B obtiene el bloqueo.
    Inserte la descripción de la imagen aquí
    Posteriormente, el hilo A finaliza la tarea, y el hilo A ejecuta la instrucción del para liberar el bloqueo. Pero en este momento, el hilo B no ha terminado de ejecutarse, y el hilo A en realidad elimina el bloqueo agregado por el hilo B.
    Inserte la descripción de la imagen aquí
    ¿Cómo evitar esta situación? Puede hacer un juicio antes de que del libere el bloqueo para verificar que el bloqueo actual es el bloqueo que agregó.

En cuanto a la implementación específica, puede usar el ID de subproceso actual como valor al bloquear y verificar que el valor correspondiente a la clave es el ID de su propio subproceso antes de eliminarlo.

Bloqueo:

String threadId = Thread.currentThread().getId()

set(key,threadId ,30,NX)

Desbloqueo:

if(threadId .equals(redisClient.get(key)){

    del(key)

}

Sin embargo, esto implica un nuevo problema: juzgar y liberar cerraduras son dos operaciones independientes, no atómicas.
Todos somos programadores que buscamos lo último, por lo que esta pieza debe implementarse con los scripts de Lua:

String luaScript = 'if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end';
redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));

De esta manera, los procesos de verificación y eliminación son operaciones atómicas.

  1. La posibilidad de concurrencia sigue
    siendo el escenario descrito en el segundo punto ahora mismo. Aunque evitamos la situación en la que el hilo A elimina la clave por error, hay dos hilos A y B que acceden al bloque de código al mismo tiempo, lo que aún no es perfecto.

Que debo hacer Podemos permitir que el subproceso que adquirió el bloqueo inicie un subproceso de daemon para "finalizar la vida útil" del bloqueo que está a punto de caducar.
Inserte la descripción de la imagen aquí
Cuando hayan pasado 29 segundos y el subproceso A no haya terminado de ejecutarse, el subproceso del demonio ejecutará la instrucción de caducidad y "continuará la vida" para el bloqueo durante 20 segundos. El hilo del demonio se ejecuta desde el 29º segundo y se ejecuta cada 20 segundos.
Inserte la descripción de la imagen aquí
Cuando el subproceso A finaliza su tarea, cerrará explícitamente el subproceso del demonio.
Inserte la descripción de la imagen aquí
Por otro lado, si el nodo 1 se apaga repentinamente, ya que el subproceso A y el subproceso del demonio están en el mismo proceso, el subproceso del demonio también se detendrá. Cuando el bloqueo expiró, nadie renovó su vida y se liberó automáticamente.
Inserte la descripción de la imagen aquí
En cuarto lugar, la consistencia final de la ejecución
最大努力通知型( Best-effort delivery) :
Fuente: máximos esfuerzos para informar del tipo
mejores esfuerzos para notificar el tipo (de entrega de mejor esfuerzo) es el más simple de una transacción flexible para algunos de los últimos tiempos consistencia baja sensibilidad de los negocios, así como el tratamiento lado pasivo El resultado no afecta el resultado del procesamiento de la parte activa. Escenarios de uso típicos: como notificaciones bancarias, notificaciones comerciales, etc. El esquema de implementación de la notificación de mejor esfuerzo generalmente cumple con las siguientes características:
1. Mensaje no confiable: la parte activa de la actividad comercial, después de completar el procesamiento comercial, envía un mensaje a la parte pasiva de la actividad comercial y no vuelve a notificar después de N veces de notificación, permitiendo el mensaje Perdido (mensaje no confiable).
2. Revisión periódica: la parte pasiva de la actividad comercial consulta a la parte activa de la actividad comercial (la parte activa proporciona la interfaz de consulta) de acuerdo con la estrategia de tiempo para recuperar el mensaje comercial perdido.

TCC两阶段补偿型:
Fuente: tipo de compensación de dos etapas TCC

1. El concepto básico de
TCC : TCC es la abreviatura de Try-Confirm-Cancel:
Etapa de prueba:
complete todas las comprobaciones comerciales (consistencia), reserve los recursos comerciales (cuasi-aislamiento)

Etapa de confirmación:
confirme la ejecución de las operaciones comerciales sin ningún control comercial, y solo use los recursos comerciales reservados en la etapa Try.

Etapa de cancelación:
cancele los recursos empresariales reservados en la etapa de prueba.

2. La diferencia entre la presentación en dos fases de TCC y XA:
Inserte la descripción de la imagen aquí

  1. En la fase 1:
    en XA, cada RM está listo para enviar su propia rama de transacción, de hecho, está listo para enviar la operación de actualización del recurso (insertar, eliminar, actualizar, etc.); en TCC, es la principal solicitud de actividad comercial (prueba) de cada Servicios empresariales de reserva de recursos.

  2. En la Fase 2:
    XA determina si debe enviar o retroceder según si cada RM se ha preparado con éxito en la primera fase. Si ambos se preparan correctamente, entonces confirme cada rama de transacción, de lo contrario, deshaga cada rama de transacción.

En TCC, si todos los recursos comerciales se reservan con éxito en la primera etapa, confirme cada servicio comercial esclavo; de lo contrario, cancele todas las solicitudes de reserva de recursos de servicios comerciales esclavos.

La diferencia entre la confirmación de dos fases de TCC y la confirmación de dos fases de XA es:
XA es una transacción distribuida a nivel de recursos y tiene una fuerte consistencia. Durante todo el proceso de confirmación de dos fases, siempre mantiene el bloqueo del recurso .
El proceso interno de la confirmación de dos fases en las transacciones XA está protegido de los desarrolladores. Cuando revisamos la especificación JTA antes, enviamos la transacción global a través del método de confirmación de UserTransaction. Esto es solo una llamada al método, y se delegará al TransactionManager para el verdadero Presentado en etapas, por lo que los desarrolladores no conocen este proceso desde el nivel del código. En el proceso de confirmación de dos fases del administrador de transacciones, desde el proceso de preparación hasta el proceso de confirmación / reversión, los recursos están realmente bloqueados. Si alguien más necesita actualizar estos dos registros, debe esperar a que se libere el bloqueo.

TCC es una transacción distribuida a nivel comercial, que en última instancia es consistente y no siempre mantendrá un bloqueo en los recursos .
La confirmación de dos fases en TCC no protege completamente al desarrollador, es decir, desde el nivel del código, el desarrollador puede sentir la existencia de la confirmación de dos fases. Como se muestra en el caso de reserva de vuelo anterior: en la primera etapa, la aerolínea debe proporcionar una interfaz de prueba (reserva de recursos de boletos). En la segunda etapa, las aerolíneas deben proporcionar una interfaz de confirmación / cancelación (confirmación de compra de boletos / cancelación de reservas). El desarrollador claramente sintió la existencia del proceso de confirmación de dos fases. Durante la ejecución de probar y confirmar / cancelar, cada transacción local generalmente se inicia para garantizar las características ACID de la lógica de negocios dentro del método. Entre ellos:
1. La transacción local en el proceso de prueba es garantizar la exactitud de la lógica empresarial de la reserva de recursos.
2. La lógica de transacción local ejecutada por confirmar / cancelar confirma / cancela los recursos reservados para asegurar la consistencia final, que es la llamada transacción basada en compensación (Transacciones basadas en compensación).
Como se trata de múltiples transacciones locales independientes, no siempre bloqueará los recursos.

Además, aquí se menciona que la transacción local realizada por confirmar / cancelar es una transacción compensatoria. En cuanto a lo que es una transacción compensatoria, el sitio web oficial de atomikos tiene la siguiente descripción:
Inserte la descripción de la imagen aquí
El contenido en el cuadro rojo es una explicación de la transacción compensatoria. El significado general es: "La compensación es una transacción local independiente que admite la función ACID. Se utiliza para cancelar lógicamente el impacto de una transacción ACID en el proveedor de servicios. Para una transacción de larga duración, es una gran Para las transacciones ACID distribuidas, es mejor usar una solución basada en compensación, y tratar cada llamada de servicio como una transacción ACID local más corta, y comprometerse inmediatamente después de la ejecución ".

Aquí, el autor entiende que confirmar y cancelar son transacciones de compensación, utilizadas para cancelar el impacto de la transacción local en la fase de prueba. Debido a que el intento de la primera etapa es solo para reservar recursos, después de eso, debe informar explícitamente al proveedor del servicio si desea este recurso o no, lo que corresponde a la confirmación / cancelación de la segunda etapa.

Consejo: Los lectores ahora deben entender por qué TCC se llama una transacción compensatoria de dos etapas. El proceso de envío se divide en dos etapas. La segunda etapa de ejecución de confirmación / cancelación es una transacción de compensación.

可靠消息最终一致性:
Fuente: coherencia final del mensaje confiable. Consistencia del
envío del mensaje: se refiere a la acción comercial de generar un mensaje y la coherencia del envío del mensaje. En otras palabras, si la operación comercial es exitosa, los mensajes generados por esta operación comercial deben entregarse con éxito (generalmente enviados a middleware de mensajes como kafka, rocketmq, rabbitmq), de lo contrario el mensaje se pierde.

Transacciones flexibles, eventual consistencia de mensajes confiables y garantía asincrónica.

Quinto, el escenario típico de transacciones distribuidas
跨库事务 : las transacciones
entre bases de datos se refieren a la aplicación de una función que necesita para operar múltiples bibliotecas, diferentes bibliotecas almacenan diferentes datos comerciales.
Inserte la descripción de la imagen aquí
分库分表:
Por lo general, una gran cantidad de datos en una biblioteca o una gran cantidad de datos esperados en el futuro se dividirán horizontalmente, es decir, se dividirán en tablas.
Inserte la descripción de la imagen aquí
服务化(SOA):
Inserte la descripción de la imagen aquí
Este artículo hace referencia al siguiente artículo:
Transacciones distribuidas

16 artículos originales publicados · Me gusta12 · Visitas137

Supongo que te gusta

Origin blog.csdn.net/qq_36435947/article/details/105565508
Recomendado
Clasificación