Architecture de transaction Apache Ignite

Dans cet article, nous examinerons le fonctionnement des transactions dans Apache Ignite . Nous ne nous attarderons pas sur le concept de stockage clé-valeur, mais allons directement à la façon dont il est implémenté dans Ignite. Commençons par un aperçu de l'architecture, puis illustrons les points clés de la logique de transaction à l'aide du traçage. Avec des exemples simples, vous verrez comment les transactions fonctionnent (et pour quelles raisons elles peuvent ne pas fonctionner).



En plus: cluster Apache Ignite



Un cluster dans Ignite est un ensemble de nœuds serveur et client , où les nœuds serveur sont combinés en une structure logique sous la forme d'un anneau et les nœuds clients sont connectés aux nœuds serveur correspondants. La principale différence entre les nœuds clients et les nœuds serveur est que les premiers ne stockent pas de données.







Les données, d'un point de vue logique, appartiennent à des partitions qui, conformément à une fonction d'affinité, sont réparties entre les nœuds ( plus d'informations sur la distribution des données dans Ignite ). Les partitions principales ( primaires ) peuvent avoir des copies ( sauvegardes ).







Fonctionnement des transactions dans Apache Ignite



L'architecture de cluster dans Apache Ignite impose une certaine exigence sur le mécanisme de transaction: la cohérence des données dans un environnement distribué. Cela signifie que les données situées sur différents nœuds doivent être modifiées de manière globale en termes de principes ACID . Il existe un certain nombre de protocoles disponibles pour faire ce que vous voulez. Apache Ignite utilise un algorithme de validation en deux phases qui se compose de deux étapes:



  • préparer;
  • commettre;


Notez que, selon le niveau d'isolement de la transaction , le mécanisme de prise de verrous et un certain nombre d'autres paramètres, les détails des phases peuvent changer.



Voyons comment les deux phases se déroulent en utilisant la transaction suivante comme exemple:



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


Préparer la phase



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




Commit



Après avoir reçu des messages de confirmation de tous les nœuds contenant des partitions primaires, le nœud coordinateur de transaction envoie un message de validation, comme illustré dans la figure ci-dessous.







Une transaction est considérée comme terminée au moment où le coordinateur de transaction a reçu tous les messages d'accusé de réception.



De la théorie à la pratique



Pour considérer la logique d'une transaction, passons au traçage.



Pour activer le traçage dans Apache Ignite, procédez comme suit:
  • Activons le module ignite-opencensus et définissons OpenCensusTracingSpi comme tracingSpi via la configuration du cluster:

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


    ou



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


  • Définissons un niveau non nul de transactions d'échantillonnage:



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


    ou



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




Tournons-nous vers le GridGain Control Center (un aperçu détaillé de l'outil) et jetons un coup d'œil à l'arborescence de span résultante: dans l'illustration, nous pouvons voir que la racine de transaction, créée au début des transactions ().











  1. La machine de capture de verrouillage initiée par l'opération put ():

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


Examinons maintenant de plus près la phase de préparation d'une transaction, qui, en commençant au nœud du coordinateur de transaction (nœud proche en termes Apache Ignite), produit la durée transactions.near.prepare.



Une fois sur la partition principale, la demande de préparation déclenche la création de l'étendue transactions.dht.prepare, dans laquelle les demandes de préparation sont envoyées aux sauvegardes tx.process.prepare.req, où elles sont traitées par tx.dht.process.prepare.response et envoyées retour à la partition primaire, qui envoie un message de confirmation au coordinateur de transaction, tout en créant un span tx.near.process.prepare.response. La phase de finition dans cet exemple sera similaire à la phase de préparation, ce qui nous évite d'avoir à effectuer une analyse détaillée.



En cliquant sur l'une des travées, nous verrons les méta-informations correspondantes:







Ainsi, par exemple, pour la durée de transaction racine, nous voyons qu'elle a été créée sur le poste client 0eefd.



Nous pouvons également augmenter la granularité du traçage des transactions en permettant le traçage du protocole de communication.



Configuration des paramètres de traçage
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())








Nous avons maintenant accès à des informations sur la transmission de messages sur le réseau entre les nœuds de cluster, ce qui, par exemple, aidera à répondre à la question de savoir si un problème potentiel a été causé par des nuances de communication réseau. Nous ne nous attarderons pas sur les détails, nous notons seulement que l'ensemble des spans socket.write et socket.read sont respectivement responsables de l'écriture sur le socket et de la lecture de l'un ou l'autre message.



Gestion des exceptions et récupération après incident



Ainsi, nous voyons que l'implémentation du protocole de transaction distribuée dans Apache Ignite est proche du canonique et vous permet d'obtenir le bon degré de cohérence des données, en fonction du niveau d'isolation de transaction sélectionné. De toute évidence, le diable est dans les détails et une grande couche de logique est restée en dehors du cadre du matériel analysé ci-dessus. Ainsi, par exemple, nous n'avons pas envisagé les mécanismes de fonctionnement et de reprise des transactions en cas de chute des nœuds qui y participent. Nous allons résoudre ce problème maintenant.



Nous avons dit plus haut que dans le contexte des transactions dans Apache Ignite, trois types de nœuds peuvent être distingués:



  • Coordinateur de transaction (près du nœud);
  • Nœud principal pour la clé correspondante (nœud principal);
  • Nœuds avec partitions de clé de sauvegarde (nœuds de sauvegarde);


et deux phases de la transaction elle-même:

  • Préparer;
  • Terminer;


Grâce à des calculs simples, nous aurons besoin de traiter six options pour les plantages de nœuds - d'une chute de sauvegarde pendant la phase de préparation à une chute du coordinateur de transaction pendant la phase de finition. Examinons ces options plus en détail.



Chute de sauvegarde à la fois sur les phases de préparation et de finition



Cette situation ne nécessite aucune action supplémentaire. Les données seront transférées aux nouveaux nœuds de sauvegarde indépendamment dans le cadre du rééquilibrage à partir du nœud principal.







Chute du nœud primaire dans la phase de préparation



S'il existe un risque de recevoir des données incohérentes, le coordinateur de transaction lève une exception. Il s'agit d'un signal de transfert de contrôle pour prendre la décision de redémarrer la transaction ou d'une autre manière de résoudre le problème à l'application cliente.







Chute du nœud principal en phase d'arrivée



Dans ce cas, le coordinateur de transaction attend des messages NodeFailureDetection supplémentaires, après réception desquels il peut décider de la réussite de la transaction, si les données ont été écrites sur les partitions de sauvegarde.







Chute du coordinateur des transactions



Le cas le plus intéressant est la perte de contexte de transaction. Dans une telle situation, les nœuds primaire et de sauvegarde échangent directement le contexte transactionnel local entre eux, restaurant ainsi le contexte global, ce qui permet de prendre une décision pour vérifier la validation. Si, par exemple, l'un des nœuds signale qu'il n'a pas reçu de message Terminer, la transaction sera annulée.







Sommaire



Dans les exemples ci-dessus, nous avons examiné le flux des transactions, en l'illustrant à l'aide du traçage, qui montre la logique interne en détail. Comme vous pouvez le voir, l'implémentation des transactions dans Apache Ignite est proche du concept classique de validation en deux phases avec quelques ajustements dans le domaine des performances des transactions liées au mécanisme de prise de verrous, aux fonctionnalités de récupération après échecs et à la logique de délai d'expiration des transactions.



All Articles