Arquitectura de transacciones de Apache Ignite

En este artículo, veremos cómo funcionan las transacciones en Apache Ignite . No nos detendremos en el concepto de almacenamiento de valores clave, sino que veremos directamente cómo se implementa en Ignite. Comencemos con una descripción general de la arquitectura y luego ilustremos los puntos clave de la lógica de transacción mediante el rastreo. Con ejemplos simples, verá cómo funcionan las transacciones (y por qué razones pueden no funcionar).



Se necesita un lado: Apache Ignite Cluster



Un clúster en Ignite es un conjunto de nodos de servidor y cliente , donde los nodos de servidor se combinan en una estructura lógica en forma de anillo, y los nodos de cliente están conectados a los nodos de servidor correspondientes. La principal diferencia entre los nodos de cliente y los nodos de servidor es que los primeros no almacenan datos.







Los datos, desde un punto de vista lógico, pertenecen a particiones que, de acuerdo con alguna función de afinidad, se distribuyen entre nodos ( más información sobre la distribución de datos en Ignite ). Las particiones primarias ( primarias ) pueden tener copias (copias de seguridad ).







Cómo funcionan las transacciones en Apache Ignite



La arquitectura de clúster en Apache Ignite impone un cierto requisito al mecanismo de transacción: consistencia de datos en un entorno distribuido. Esto significa que los datos ubicados en diferentes nodos deben cambiarse de manera integral en términos de los principios ACID . Hay varios protocolos disponibles para hacer lo que desee. Apache Ignite utiliza un algoritmo de confirmación de dos fases que consta de dos etapas:



  • preparar;
  • cometer;


Tenga en cuenta que, según el nivel de aislamiento de la transacción , el mecanismo para realizar bloqueos y una serie de otros parámetros, los detalles de las fases pueden cambiar.



Veamos cómo se llevan a cabo ambas fases utilizando la siguiente transacción como ejemplo:



Transaction tx = client.transactions().txStart(PESSIMISTIC, READ_COMMITTED);
client.cache(DEFAULT_CACHE_NAME).put(1, 1);
tx.commit();


Fase de preparación



  1. — (near node Apache Ignite) — prepare- , primary- , .
  2. primary- Prepare- backup-, , . backup- .
  3. backup- Acknowledge- primary-, , , , .




Commit



Después de recibir mensajes de confirmación de todos los nodos que contienen particiones primarias, el nodo coordinador de transacciones envía un mensaje de confirmación, como se muestra en la figura siguiente.







Una transacción se considera completa en el momento en que el coordinador de transacciones ha recibido todos los mensajes de confirmación.



De la teoría a la práctica



Para considerar la lógica de una transacción, pasemos al rastreo.



Para habilitar el seguimiento en Apache Ignite, siga estos pasos:
  • Habilitemos el módulo ignite-opencensus y configuremos OpenCensusTracingSpi como tracingSpi a través de la configuración del clúster:

    <bean class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="tracingSpi">
            <bean class="org.apache.ignite.spi.tracing.opencensus.OpenCensusTracingSpi"/>
        </property>
    </bean>
    


    o



    IgniteConfiguration cfg = new IgniteConfiguration();
    
    cfg.setTracingSpi(
        new org.apache.ignite.spi.tracing.opencensus.OpenCensusTracingSpi());
    


  • Establezcamos un nivel distinto de cero de transacciones de muestreo:



    JVM_OPTS="-DIGNITE_ENABLE_EXPERIMENTAL_COMMAND=true" ./control.sh --tracing-configuration set --scope TX --sampling-rate 1
    


    o



    ignite.tracingConfiguration().set(
                new TracingConfigurationCoordinates.Builder(Scope.TX).build(),
                new TracingConfigurationParameters.Builder().
                        withSamplingRate(SAMPLING_RATE_ALWAYS).build());
    


    :



    • API
      JVM_OPTS="-DIGNITE_ENABLE_EXPERIMENTAL_COMMAND=true"
    • sampling-rate , , . , .
    • , SPI, . , , .


  • PESSIMISTIC, SERIALIZABLE .



    Transaction tx = client.transactions().txStart(PESSIMISTIC, SERIALIZABLE);
    client.cache(DEFAULT_CACHE_NAME).put(1, 1);
    tx.commit();




Pasemos al GridGain Control Center (una descripción detallada de la herramienta) y echemos un vistazo al span tree resultante: En la ilustración, podemos ver que el span raíz de la transacción, creado al comienzo de las transacciones (). La llamada TxStart, genera dos grupos de span condicionales:











  1. La máquina de agarrar cerraduras iniciada por la operación put ():

    1. transactions.near.enlist.write
    2. transactions.colocated.lock.map
  2. transactions.commit, tx.commit(), , , — prepare finish Apache Ignite (finish- commit- ).


Echemos ahora un vistazo más de cerca a la fase de preparación de una transacción, que, comenzando en el nodo coordinador de transacciones (nodo cercano en términos de Apache Ignite), produce tramo de transacciones.near.prepare.



Una vez en la partición primaria, la solicitud de preparación activa la creación de las transacciones.dht.prepare span, dentro del cual las solicitudes de preparación se envían a las copias de seguridad de tx.process.prepare.req, donde tx.dht.process.prepare.response las procesa y envía de regreso a la partición primaria, que envía un mensaje de confirmación al coordinador de transacciones, a lo largo del camino creando un intervalo tx.near.process.prepare.response. La fase de finalización en este ejemplo será similar a la fase de preparación, lo que nos ahorra la necesidad de un análisis detallado.



Al hacer clic en cualquiera de los intervalos, veremos la metainformación correspondiente:







Entonces, por ejemplo, para el intervalo de transacciones raíz, vemos que se creó en el nodo cliente 0eefd.



También podemos aumentar la granularidad del rastreo de transacciones habilitando el rastreo del protocolo de comunicación.



Configurar parámetros de seguimiento
JVM_OPTS="-DIGNITE_ENABLE_EXPERIMENTAL_COMMAND=true" ./control.sh --tracing-configuration set --scope TX --included-scopes Communication --sampling-rate 1 --included-scopes COMMUNICATION




       ignite.tracingConfiguration().set(
           new TracingConfigurationCoordinates.Builder(Scope.TX).build(),
           new TracingConfigurationParameters.Builder().
               withIncludedScopes(Collections.singleton(Scope.COMMUNICATION)).
               withSamplingRate(SAMPLING_RATE_ALWAYS).build())








Ahora tenemos acceso a información sobre la transmisión de mensajes a través de la red entre los nodos del clúster, lo que, por ejemplo, ayudará a responder la pregunta de si un problema potencial fue causado por matices de la comunicación de la red. No nos detendremos en los detalles, solo notamos que muchos intervalos de socket.write y socket.read son responsables de escribir y leer un mensaje, respectivamente.



Manejo de excepciones y recuperación de fallas



Así, vemos que la implementación del protocolo de transacciones distribuidas en Apache Ignite se acerca a lo canónico y le permite obtener el grado adecuado de consistencia de datos, dependiendo del nivel de aislamiento de transacción seleccionado. Obviamente, el diablo está en los detalles y una gran capa de lógica quedó fuera del alcance del material analizado anteriormente. Entonces, por ejemplo, no hemos considerado los mecanismos de operación y recuperación de transacciones en caso de caída de los nodos que participan en ella. Arreglaremos esto ahora.



Dijimos anteriormente que en el contexto de transacciones en Apache Ignite, se pueden distinguir tres tipos de nodos:



  • Coordinador de transacciones (nodo cercano);
  • Nodo principal para la clave correspondiente (nodo principal);
  • Nodos con particiones clave de respaldo (nodos de respaldo);


y dos fases de la transacción en sí:

  • Preparar;
  • Terminar;


A través de cálculos simples, tendremos la necesidad de procesar seis opciones para bloqueos de nodos, desde una caída de respaldo durante la fase de preparación hasta una caída del coordinador de transacciones durante la fase de finalización. Consideremos estas opciones con más detalle.



Caída de respaldo tanto en las fases de preparación como en las de acabado



Esta situación no requiere ninguna acción adicional. Los datos se transferirán a los nuevos nodos de respaldo de forma independiente como parte del reequilibrio del nodo principal.







Caída del nodo primario en la fase de preparación



Si existe el riesgo de recibir datos inconsistentes, el coordinador de transacciones lanza una excepción. Esta es una señal para que la transferencia de control tome la decisión de reiniciar la transacción u otra forma de resolver el problema en la aplicación cliente.







Caída del nodo primario en la fase final.



En este caso, el coordinador de transacciones espera mensajes adicionales de NodeFailureDetection, después de recibirlos, puede decidir sobre la finalización exitosa de la transacción, si los datos se escribieron en particiones de respaldo.







Caída del coordinador de transacciones



El caso más interesante es la pérdida del contexto de la transacción. En tal situación, los nodos primario y de respaldo intercambian directamente el contexto transaccional local entre sí, restaurando así el contexto global, lo que permite tomar una decisión para verificar el compromiso. Si, por ejemplo, uno de los nodos informa que no recibió un mensaje Finalizar, la transacción se revertirá.







Resumen



En los ejemplos anteriores, examinamos el flujo de transacciones, ilustrándolo mediante el rastreo, que muestra la lógica interna en detalle. Como puede ver, la implementación de transacciones en Apache Ignite se acerca al concepto clásico de compromiso en dos fases con algunos ajustes en el campo del rendimiento de las transacciones relacionados con el mecanismo de toma de bloqueos, características de recuperación después de fallas y lógica de tiempo de espera de transacciones.



All Articles