Confirmación de dos fases de Flink

1. Extensión (qué es la transacción XA)

XA (Arquitectura extendida) se refiere a la especificación del procesamiento de transacciones distribuidas propuesto por la organización X/Open. XA es un protocolo de transacciones distribuidas propuesto por Tuxedo, por lo que las transacciones distribuidas también se denominan transacciones XA. El protocolo XA define principalmente la interfaz entre el administrador de transacciones TM (Transaction Manager, coordinador) y el administrador de recursos RM (Resource Manager, participante). Entre ellos, los administradores de recursos a menudo son implementados por bases de datos, como Oracle, DB2 y MySQL.Todas estas bases de datos comerciales implementan interfaces XA, y los administradores de transacciones, como programador global, son responsables del envío y reversión de varios recursos locales. Las transacciones XA se implementan con base en el protocolo de compromiso de dos fases (Two-phaseCommit, 2PC), que puede garantizar la sólida consistencia de los datos.Muchos sistemas de administración de datos relacionales distribuidos usan este protocolo para completar la distribución. La fase 1 es la fase de preparación, es decir, todos los participantes están listos para ejecutar la transacción y bloquear los recursos necesarios. Cuando el participante esté Listo, informe al TM que está listo. La fase dos es la fase de presentación. Cuando TM confirma que todos los participantes están listos, envía el comando COMMIT a todos los participantes.

Las transacciones XA permiten transacciones distribuidas de diferentes bases de datos, siempre que cada nodo que participa en la transacción global admita transacciones XA. Oracle, MySQL y SQL Server admiten transacciones XA.

La transacción XA consta de uno o más administradores de recursos (RM), un administrador de transacciones (TM) y una aplicación (ApplicationProgram).

  • Administrador de recursos: proporciona métodos para acceder a los recursos transaccionales. Por lo general, una base de datos es un administrador de recursos.

  • Gestor de transacciones: coordina las distintas transacciones que participan en la transacción global. Necesidad de comunicarse con todos los administradores de recursos que participan en la transacción global.

  • Aplicación: Define los límites de las transacciones.

La desventaja de las transacciones XA es que el rendimiento no es bueno y no puede cumplir con escenarios de alta concurrencia. El rendimiento de las transacciones entre una base de datos y las transacciones XA entre varias bases de datos será muy diferente. Por lo tanto, trate de evitar transacciones XA, como escribir datos localmente, distribuir datos con un sistema de mensajes de alto rendimiento o usar tecnologías como la replicación de bases de datos. Use XA solo cuando los requisitos comerciales no puedan cumplirse por otros medios y el rendimiento no sea el cuello de botella.

2. Presentación en dos fases de Flink

2.1 Introducción a la semántica EXACTLY_ONCE

La semántica de EXACTLY_ONCE se conoce como EOS, lo que significa que cada mensaje de entrada solo afectará el resultado final una vez. Tenga en cuenta que se ve afectado una vez, no procesado una vez. Flink siempre ha afirmado que es compatible con EOS. De hecho, es principalmente para el interior de la aplicación Flink Para sistemas externos (extremo a extremo) tienen restricciones más fuertes

La escritura del sistema externo admite la idempotencia
Los sistemas externos admiten la escritura de manera transaccional.Flink
introdujo la interfaz TwoPhaseCommitSinkFunction en la versión 1.4.0 y la implementó en el conector de Kafka Producer, admitiendo la semántica EXACTLY_ONCE para Kafka Sink externo.

详见:Procesamiento de extremo a extremo exactamente una vez en Apache Flink

2.2 Idempotencia y transaccionalidad de Kafka

Se ha propuesto en la versión 0.11 de Kafka que Kafka admita transacciones e idempotencia, haciendo posible la semántica de Kafka de extremo a extremo exactamente una vez. Tanto la idempotencia como la transaccionalidad son muy importantes en el desarrollo de Kafka.

En circunstancias normales, el productor entrega un mensaje al intermediario, y el intermediario agrega el mensaje al flujo correspondiente (es decir, una partición de un tema determinado) y devuelve una señal ACK al productor para confirmar la recepción.

1. Realización de la idempotencia
Para realizar la idempotencia, Kafka introdujo Producer y SequenceNumber en la arquitectura de diseño subyacente.
(1), ID de productor: cuando se inicializa cada nuevo productor, o se le asigna un ID de productor único, este ID de productor es invisible para el usuario del cliente.
(2), número de secuencia: para cada ID de productor, cada tema y partición a los que el productor envía datos corresponde a un valor de número de secuencia que aumenta desde 0.
2. Problemas resueltos tras la introducción de la idempotencia.
Enviar los mismos datos a kafka agregará Pid y secuenciaId a los datos

2. Asuntos

En el aseguramiento de datos de extremo a extremo, otro tema al que prestamos especial atención son las transacciones. Esa es la operación atómica. El resultado correspondiente es el éxito o el fracaso al mismo tiempo.La transacción de Kafka se centra en la operación atómica de producción y consumo. Un ejemplo típico es.
Una serie de operaciones del Productor que produce mensajes y consume mensajes para enviar Compensaciones están en una sola transacción.

Ejemplos de escenarios generados incluyen:

(1) El productor envía mensajes varias veces y los encapsula en una operación atómica, es decir, se requiere que tenga éxito o falle al mismo tiempo.
(2) En el modo de consumidor y productor, debido a que el consumidor tiene un problema con las compensaciones de compromiso, lo que conduce al consumo repetido de mensajes, es necesario encapsular las operaciones de Compensación de consumidor y Commit en este modo y una serie de operaciones de Producer en una serie de mensajes de producción Una operación atómica.
(3), la transacción general de Kafka se puede dividir en tres aspectos:

  • 1) Solo el Productor produce mensajes, y este escenario requiere la intervención de transacciones;
  • 2) Los mensajes de consumo y los mensajes de producción coexisten, como el modo Consumidor y Productor. Este escenario es un modo relativamente común en los proyectos generales de Kafka y requiere la intervención de transacciones;
  • 3), pero solo el Consumidor consume el mensaje. Esta operación tiene poco significado en el proyecto real. Es lo mismo que el resultado de Commit Offsets manual, y este escenario no es el propósito de introducir la transacción.

5 métodos API proporcionados por la transacción:

org.apache.kafka.clients.producer.Producer<K,V>接口中:



// 1. 初始化事务,需要注意确保transation.id属性被分配
void initTransactions();

// 2. 开启事务
void beginTransaction() throws ProducerFencedException;

// 3. 为Consumer提供的在事务内Commit Offsets的操作
void sendOffsetsToTransaction(Map<TopicPartition, OffsetAndMetadata> offsets,
                              String consumerGroupId) throws ProducerFencedException;

// 4. 提交事务
void commitTransaction() throws ProducerFencedException;

// 5. 放弃事务,类似于回滚事务的操作
void abortTransaction() throws ProducerFencedException;

Para más detalles, ver: idempotencia y transaccionalidad de Kafka

2.3 Presentación en dos fases de Flink

descripción de la escena

La confirmación en dos fases (2PC) es el protocolo de consenso distribuido más básico y se usa ampliamente.

Introducción de 2 piezas:

2PC En un sistema distribuido, para que cada nodo perciba la ejecución de transacciones de todos los demás nodos, necesitamos introducir un nodo central para unificar la lógica de ejecución y el progreso de todos los nodos.Este nodo central se llama y reporta al nodo central 协调者(Coordinator)u Otros nodos planificados por el nodo central se denominan 参与者(Participant).

principio 2PC

①, etapa de solicitud

  • 1. El coordinador envía solicitudes de preparación y contenido de transacciones a todos los participantes, pregunta si pueden preparar envíos de transacciones y espera las respuestas de los participantes.
  • 2. El participante realiza la operación incluida en la transacción y registra el registro de deshacer (para retroceder) y el registro de rehacer (para reproducir), pero en realidad no se compromete.
  • 3. El participante devuelve el resultado de ejecución de la transacción al coordinador, y la ejecución devuelve sí, en caso contrario devuelve no.

②, Fase de envío (dividida en dos casos de éxito y fracaso )

Caso 1: Si todos los participantes devuelven sí, significa que la transacción se puede comprometer.

  • 1. El coordinador envía una solicitud de compromiso a todos los participantes.
  • 2. Después de recibir la solicitud de confirmación, el participante envía la transacción, libera los recursos de transacción ocupados y devuelve un acuse de recibo al coordinador.
  • 3. El coordinador recibe mensajes de acuse de recibo de todos los participantes y la transacción se completa con éxito.

Caso 2: si un participante no devuelve o no regresa después de un tiempo de espera, significa que la transacción finaliza y debe revertirse.

  • 1. El coordinador envía una solicitud de reversión a todos los participantes.
  • 2. Después de recibir la solicitud de reversión, el participante retrocede al estado anterior a la ejecución de la transacción de acuerdo con el registro de deshacer, libera los recursos de transacción ocupados y devuelve un acuse de recibo al coordinador.
  • 3. El coordinador recibe el mensaje de acuse de recibo de todos los participantes y se completa la reversión de la transacción.

inserte la descripción de la imagen aquí

Ventajas y desventajas de 2pc

La ventaja de 2PC es que el principio es muy simple, fácil de entender e implementar.
Hay tres desventajas principales, que se enumeran a continuación:
(1) El coordinador tiene un problema de un solo punto. Si el coordinador cuelga, toda la lógica de 2PC no puede ejecutarse en absoluto.
(2), el proceso de ejecución es completamente síncrono. Cada participante está en un estado bloqueado mientras espera que otros participantes respondan, y hay problemas de rendimiento en caso de gran simultaneidad.
(3) Todavía existe el riesgo de inconsistencia. Si solo algunos participantes reciben la solicitud de compromiso debido a accidentes como anomalías en la red, algunos participantes enviarán transacciones mientras que otros no.
Sin embargo, ahora la gente ha trabajado mucho en el campo de la consistencia distribuida, y hay innumerables marcos de coordinación distribuidos representados por ZooKeeper.Con estas bendiciones, 2PC ha mejorado enormemente la confiabilidad y puede usarse en entornos de producción exigentes.

Implementación basada en 2PC de Flink

El escenario de aplicación más común de 2PC es en realidad una base de datos relacional, como el sistema de transacciones XA del motor de almacenamiento mysql InnoDB.
Como motor de procesamiento de secuencias, Flink proporciona naturalmente una garantía para la semántica exactamente una vez. El mecanismo de punto de control de intención interna de Flink y el algoritmo ligero de instantáneas distribuidas ABS garantizan exactamente una vez. En segundo lugar, si queremos lograr exactamente una lógica de salida de extremo a extremo, debemos imponer una de las siguientes dos restricciones: escritura idempotente (escritura idempotente), escritura transaccional (escritura transaccional).

En Spark Streaming, depende totalmente del usuario implementar la escritura transaccional y el marco en sí no proporciona ninguna implementación. Sin embargo, Flink proporciona una SinkFunction basada en 2PC llamada TwoPhaseCommitSinkFunction, que nos ayuda a realizar un trabajo básico.

inserte la descripción de la imagen aquí

Flink recomienda oficialmente que toda la lógica de sumidero que necesite garantizar exactamente una vez herede esta clase abstracta. Define específicamente los siguientes cuatro métodos abstractos. Necesitamos implementarlo en subclases.

// 开始一个事务,返回事务信息的句柄
    protected abstract TXN beginTransaction() throws Exception;

// 预提交(即提交请求)阶段的逻辑
    protected abstract void preCommit(TXN transaction) throws Exception;
 
// 正式提交阶段的逻辑
   protected abstract void commit(TXN transaction);

// 取消事务
   protected abstract void abort(TXN transaction);

public class FlinkKafkaProducer<IN>
        extends TwoPhaseCommitSinkFunction<
                IN,
                FlinkKafkaProducer.KafkaTransactionState,
                FlinkKafkaProducer.KafkaTransactionContext> {
    
    

    /**
     * Semantics that can be chosen.
     * <li>{@link #EXACTLY_ONCE}
     * <li>{@link #AT_LEAST_ONCE}
     * <li>{@link #NONE}
     */
    public enum Semantic {
    
    

        /**
         * Semantic.EXACTLY_ONCE the Flink producer will write all messages in a Kafka transaction
         * that will be committed to Kafka on a checkpoint.
         *
         * <p>In this mode {@link FlinkKafkaProducer} sets up a pool of {@link
         * FlinkKafkaInternalProducer}. Between each checkpoint a Kafka transaction is created,
         * which is committed on {@link 

FlinkKafkaProducer#notifyCheckpointComplete(long)}


. If
         * checkpoint complete notifications are running late, {@link FlinkKafkaProducer} can run
         * out of {@link FlinkKafkaInternalProducer}s in the pool. In that case any subsequent
         * {@link FlinkKafkaProducer#snapshotState(FunctionSnapshotContext)} requests will fail and
         * {@link FlinkKafkaProducer} will keep using the {@link FlinkKafkaInternalProducer} from
         * the previous checkpoint. To decrease the chance of failing checkpoints there are four
         * options:
         * <li>decrease number of max concurrent checkpoints
         * <li>make checkpoints more reliable (so that they complete faster)
         * <li>increase the delay between checkpoints
         * <li>increase the size of {@link FlinkKafkaInternalProducer}s pool
         */
        EXACTLY_ONCE,

        /**
         * Semantic.AT_LEAST_ONCE the Flink producer will wait for all outstanding messages in the
         * Kafka buffers to be acknowledged by the Kafka producer on a checkpoint.
         */
        AT_LEAST_ONCE,

        /**
         * Semantic.NONE means that nothing will be guaranteed. Messages can be lost and/or
         * duplicated in case of failure.
         */
        NONE
    }

A continuación se utiliza la integración de Flink y Kafka para ilustrar el proceso específico de 2PC. Tenga en cuenta que la versión de Kafka aquí debe ser 0.11 y superior, porque solo la versión 0.11+ es compatible con el productor idempotente y transaccional, por lo que 2PC tiene significado. El mecanismo transaccional interno de Kafka se muestra en el siguiente diagrama de bloques.

La implementación específica de la confirmación de dos fases de flink es la siguiente:
el método FlinkKafkaProducer.commit() en realidad actúa como proxy del método KafkaProducer.commitTransaction() para enviar formalmente la transacción a Kafka.

Flink versión: 1.13.6

	@Override
    protected void commit(FlinkKafkaProducer.KafkaTransactionState transaction) {
    
    
        if (transaction.isTransactional()) {
    
    
            try {
    
    
                transaction.producer.commitTransaction();
            } finally {
    
    
                recycleTransactionalProducer(transaction.producer);
            }
        }
    }

El punto de llamada de este método está en el método TwoPhaseCommitSinkFunction.notifyCheckpointComplete() Como su nombre lo indica, se llamará a este método cuando todos los puntos de control sean exitosos.

	@Override
    public final void notifyCheckpointComplete(long checkpointId) throws Exception {
    
    
        // the following scenarios are possible here
        //
        //  (1) there is exactly one transaction from the latest checkpoint that
        //      was triggered and completed. That should be the common case.
        //      Simply commit that transaction in that case.
        //
        //  (2) there are multiple pending transactions because one previous
        //      checkpoint was skipped. That is a rare case, but can happen
        //      for example when:
        //
        //        - the master cannot persist the metadata of the last
        //          checkpoint (temporary outage in the storage system) but
        //          could persist a successive checkpoint (the one notified here)
        //
        //        - other tasks could not persist their status during
        //          the previous checkpoint, but did not trigger a failure because they
        //          could hold onto their state and could successfully persist it in
        //          a successive checkpoint (the one notified here)
        //
        //      In both cases, the prior checkpoint never reach a committed state, but
        //      this checkpoint is always expected to subsume the prior one and cover all
        //      changes since the last successful one. As a consequence, we need to commit
        //      all pending transactions.
        //
        //  (3) Multiple transactions are pending, but the checkpoint complete notification
        //      relates not to the latest. That is possible, because notification messages
        //      can be delayed (in an extreme case till arrive after a succeeding checkpoint
        //      was triggered) and because there can be concurrent overlapping checkpoints
        //      (a new one is started before the previous fully finished).
        //
        // ==> There should never be a case where we have no pending transaction here
        //

        Iterator<Map.Entry<Long, TransactionHolder<TXN>>> pendingTransactionIterator =
                pendingCommitTransactions.entrySet().iterator();
        Throwable firstError = null;

        while (pendingTransactionIterator.hasNext()) {
    
    
            Map.Entry<Long, TransactionHolder<TXN>> entry = pendingTransactionIterator.next();
            Long pendingTransactionCheckpointId = entry.getKey();
            TransactionHolder<TXN> pendingTransaction = entry.getValue();
            if (pendingTransactionCheckpointId > checkpointId) {
    
    
                continue;
            }

            LOG.info(
                    "{} - checkpoint {} complete, committing transaction {} from checkpoint {}",
                    name(),
                    checkpointId,
                    pendingTransaction,
                    pendingTransactionCheckpointId);

            logWarningIfTimeoutAlmostReached(pendingTransaction);
            try {
    
    
                commit(pendingTransaction.handle);
            } catch (Throwable t) {
    
    
                if (firstError == null) {
    
    
                    firstError = t;
                }
            }

            LOG.debug("{} - committed checkpoint transaction {}", name(), pendingTransaction);

            pendingTransactionIterator.remove();
        }

        if (firstError != null) {
    
    
            throw new FlinkRuntimeException(
                    "Committing one of transactions failed, logging first encountered failure",
                    firstError);
        }
    }

Se puede ver en el código que este método extrae un identificador de transacción que espera ser enviado cada vez, verifica su ID de punto de control y llama al método commit() para enviar. El diagrama de flujo de esta etapa es el siguiente:

inserte la descripción de la imagen aquí

Se puede ver que solo bajo la premisa de que todos los puntos de control son exitosos, la escritura tendrá éxito. Esto está en línea con el proceso 2PC descrito anteriormente. Entre ellos, el administrador de trabajos es el coordinador, y cada operador es un participante, y un participante en el sumidero ejecutará el envío. Una vez que falla un punto de control, no se ejecutará el método de notificaciónCheckpointComplete (). Si el reintento no tiene éxito, se llamará al método abort () para revertir la transacción por fin, de la siguiente manera:

	@Override
    protected void abort(FlinkKafkaProducer.KafkaTransactionState transaction) {
    
    
        if (transaction.isTransactional()) {
    
    
            transaction.producer.abortTransaction();
            recycleTransactionalProducer(transaction.producer);
        }
    }

Lista de referencia:


Supongo que te gusta

Origin blog.csdn.net/liuwei0376/article/details/126259056
Recomendado
Clasificación