Arquitetura de transação Apache Ignite

Neste artigo, vamos dar uma olhada em como as transações funcionam no Apache Ignite . Não nos deteremos no conceito de armazenamento de valor-chave, mas iremos direto para como ele é implementado no Ignite. Vamos começar com uma visão geral da arquitetura e, em seguida, ilustrar os principais pontos da lógica de transação usando rastreio. Com exemplos simples, você verá como as transações funcionam (e por quais motivos podem não funcionar).



À parte necessária: Apache Ignite Cluster



Um cluster no Ignite é um conjunto de nós de servidor e cliente , onde os nós de servidor são combinados em uma estrutura lógica na forma de um anel e os nós de cliente são conectados aos nós de servidor correspondentes. A principal diferença entre nós de clientes e nós de servidor é que os primeiros não armazenam dados.







Os dados, de um ponto de vista lógico, pertencem a partições, que, de acordo com alguma função de afinidade, são distribuídas entre os nós ( mais sobre distribuição de dados no Ignite ). Nas partições principais ( primárias ) podem ser cópias ( backups ).







Como as transações funcionam no Apache Ignite



A arquitetura de cluster no Apache Ignite impõe um certo requisito ao mecanismo de transação: consistência de dados em um ambiente distribuído. Isso significa que os dados localizados em nós diferentes devem ser alterados de maneira holística em termos dos princípios do ACID . Existem vários protocolos disponíveis para fazer o que você deseja. O Apache Ignite usa um algoritmo de confirmação de duas fases que consiste em dois estágios:



  • preparar;
  • comprometer;


Observe que, dependendo do nível de isolamento da transação , do mecanismo de bloqueio e de vários outros parâmetros, os detalhes nas fases podem mudar.



Vamos ver como as duas fases ocorrem usando a seguinte transação como exemplo:



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


Fase de preparação



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




Commit



Depois de receber mensagens de confirmação de todos os nós contendo partições primárias, o nó coordenador da transação envia uma mensagem Confirmar, conforme mostrado na figura abaixo.







Uma transação é considerada concluída no momento em que o coordenador da transação recebe todas as mensagens de confirmação.



Da teoria à prática



Para considerar a lógica de uma transação, vamos voltar ao rastreamento.



Para ativar o rastreamento no Apache Ignite, siga estas etapas:
  • ignite-opencensus OpenCensusTracingSpi tracingSpi :

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






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


  • :



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






    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();




Vamos voltar ao GridGain Control Center (uma visão geral detalhada da ferramenta) e dar uma olhada na árvore de expansão resultante: Na ilustração, podemos ver que a extensão da raiz da transação, criada no início das transações (). A chamada TxStart, gera dois grupos de extensão condicional:











  1. A máquina de captura de bloqueio iniciada pela operação put ():

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


Vamos agora dar uma olhada mais de perto na fase de preparação de uma transação, que, começando no nó coordenador da transação (próximo ao nó nos termos do Apache Ignite), produz a extensão transactions.near.prepare.



Uma vez na partição primária, a solicitação de preparação aciona a criação de intervalo transactions.dht.prepare, dentro da qual as solicitações de preparação são enviadas para os backups tx.process.prepare.req, onde são processadas por tx.dht.process.prepare.response e enviadas de volta para a partição primária, que envia uma mensagem de confirmação para o coordenador da transação, ao longo do caminho criando um span tx.near.process.prepare.response. A fase de conclusão neste exemplo será semelhante à fase de preparação, o que nos livra da necessidade de uma análise detalhada.



Ao clicar em qualquer um dos spans, veremos as meta informações correspondentes:







Assim, por exemplo, para a extensão da transação raiz, vemos que ela foi criada no nó cliente 0eefd.



Também podemos aumentar a granularidade do rastreamento da transação, permitindo o rastreamento do protocolo de comunicação.



Configurando parâmetros de rastreamento
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())








Agora temos acesso a informações sobre a transmissão de mensagens pela rede entre os nós do cluster, o que, por exemplo, ajudará a responder à pergunta se um problema potencial foi causado por nuances de comunicação da rede. Não vamos insistir nos detalhes, apenas observamos que o conjunto de spans socket.write e socket.read são responsáveis ​​por escrever no soquete e ler esta ou aquela mensagem, respectivamente.



Tratamento de exceções e recuperação de falhas



Assim, vemos que a implementação do protocolo de transação distribuída no Apache Ignite é próxima à canônica e permite obter o grau adequado de consistência dos dados, dependendo do nível de isolamento da transação selecionada. Obviamente, o diabo está nos detalhes e uma grande camada de lógica ficou fora do escopo do material analisado acima. Assim, por exemplo, não consideramos os mecanismos de operação e recuperação de transações em caso de queda dos nós participantes dela. Vamos consertar isso agora.



Dissemos acima que, no contexto de transações no Apache Ignite, três tipos de nós podem ser distinguidos:



  • Coordenador de transações (próximo ao nó);
  • Nó primário para a chave correspondente (nó primário);
  • Nós com partições de chave de backup (nós de backup);


e duas fases da própria transação:

  • Preparar;
  • Terminar;


Por meio de cálculos simples, obteremos a necessidade de processar seis opções para travamentos de nó - de uma queda de backup durante a fase de preparação a uma queda do coordenador de transação durante a fase de conclusão. Vamos considerar essas opções com mais detalhes.



Caindo backup nas fases de preparação e final



Esta situação não requer nenhuma ação adicional. Os dados serão transferidos para os novos nós de backup de forma independente como parte do rebalanceamento do nó primário.







Nó primário em queda na fase de preparação



Se houver risco de receber dados inconsistentes, o coordenador da transação lança uma exceção. Este é um sinal para transferir o controle para tomar a decisão de reiniciar a transação ou outra forma de resolver o problema para o aplicativo cliente.







Queda do nó primário na fase de acabamento



Neste caso, o coordenador da transação aguarda por mensagens NodeFailureDetection adicionais, após o recebimento pode decidir sobre a conclusão com sucesso da transação, caso os dados tenham sido gravados nas partições de backup.







Queda do coordenador da transação



O caso mais interessante é a perda do contexto da transação. Em tal situação, os nós primário e de backup trocam diretamente o contexto transacional local entre si, restaurando assim o contexto global, o que permite que seja tomada uma decisão para verificar o commit. Se, por exemplo, um dos nós relatar que não recebeu uma mensagem Concluir, a transação será revertida.







Resumo



Nos exemplos acima, examinamos o fluxo de transações, ilustrando-o por meio de rastreamento, que mostra a lógica interna em detalhes. Como você pode ver, a implementação de transações no Apache Ignite se aproxima do conceito clássico de two-phase commit com alguns ajustes no campo de desempenho de transações relacionados ao mecanismo de obtenção de bloqueios, recursos de recuperação após falhas e lógica de tempo limite de transação.



All Articles