Introduction à Atomix

Introduction à Atomix

1. Vue d'ensemble

La plupart des applications distribuées nécessitent que certains composants avec état soient cohérents et tolérants aux pannes. Atomix is an embeddable library helping in achieving fault-tolerance and consistency for distributed resources.

Il fournit un ensemble complet d'API pour la gestion de ses ressources, telles que des collections, des groupes et des outils pour l'accès simultané.

Pour commencer, nous devons ajouter la dépendance Maven suivante à notre pom:


    io.atomix
    atomix-all
    1.0.8

Cette dépendance fournit un transport basé sur Netty nécessaire à ses nœuds pour communiquer entre eux.

2. Amorcer un cluster

Pour démarrer avec Atomix, nous devons d’abord amorcer un cluster.

Atomix consiste en un ensemble de répliques utilisées pour créer des ressources réparties avec état. Each replica maintains a copy of the state of each resource existing in the cluster.

Les répliques sont deux types dans un cluster: actif et passif.

Les changements d'état des ressources distribuées sont propagés via des réplicas actifs, tandis que les réplicas passifs sont synchronisés pour maintenir la tolérance aux pannes.

2.1. Amorcer un cluster intégré

Pour amorcer un cluster à nœud unique, nous devons d'abord créer une instance deAtomixReplica:

AtomixReplica replica = AtomixReplica.builder(
  new Address("localhost", 8700))
   .withStorage(storage)
   .withTransport(new NettyTransport())
   .build();

Ici, le réplica est configuré avecStorage etTransport. Extrait de code pour déclarer le stockage:

Storage storage = Storage.builder()
  .withDirectory(new File("logs"))
  .withStorageLevel(StorageLevel.DISK)
  .build();

Une fois la réplique déclarée et configurée avec stockage et transport, nous pouvons l'amorcer en appelant simplementbootstrap() - qui renvoie unCompletableFuture qui peut être utilisé pour bloquer jusqu'à ce que le serveur soit amorcé en appelant le blocage associéjoin() méthode:

CompletableFuture future = replica.bootstrap();
future.join();

Jusqu'à présent, nous avons construit un cluster à un seul nœud. Nous pouvons maintenant y ajouter plus de nœuds.

Pour ce faire, nous devons créer d'autres réplicas et les joindre au cluster existant; nous devons créer un nouveau thread pour appeler la méthodejoin(Address):

AtomixReplica replica2 = AtomixReplica.builder(
  new Address("localhost", 8701))
    .withStorage(storage)
    .withTransport(new NettyTransport())
    .build();

replica2
  .join(new Address("localhost", 8700))
  .join();

AtomixReplica replica3 = AtomixReplica.builder(
  new Address("localhost", 8702))
    .withStorage(storage)
    .withTransport(new NettyTransport())
    .build();

replica3.join(
  new Address("localhost", 8700),
  new Address("localhost", 8701))
  .join();

Nous avons maintenant un cluster à trois nœuds amorcé. Alternativement, nous pouvons amorcer un cluster en passant unList d'adresses dans la méthodebootstrap(List<Address>):

List
cluster = Arrays.asList( new Address("localhost", 8700), new Address("localhost", 8701), new Address("localhsot", 8702)); AtomixReplica replica1 = AtomixReplica .builder(cluster.get(0)) .build(); replica1.bootstrap(cluster).join(); AtomixReplica replica2 = AtomixReplica .builder(cluster.get(1)) .build(); replica2.bootstrap(cluster).join(); AtomixReplica replica3 = AtomixReplica .builder(cluster.get(2)) .build(); replica3.bootstrap(cluster).join();

Nous devons créer un nouveau thread pour chaque réplique.

2.2. Amorcer un cluster autonome

Le serveur Atomix peut être exécuté en tant que serveur autonome téléchargeable à partir de Maven Central. En termes simples, il s’agit d’une archive Java qui peut être exécutée via le terminal en fournissant

En termes simples - c'est une archive Java qui peut être exécutée via le terminal en fournissant le paramètrehost: port dans l'indicateur d'adresse et en utilisant l'indicateur-bootstrap.

Voici la commande pour amorcer un cluster:

java -jar atomix-standalone-server.jar
  -address 127.0.0.1:8700 -bootstrap -config atomix.properties

Iciatomix.properties est le fichier de configuration pour configurer le stockage et le transport. Pour créer un cluster multinœud, nous pouvons ajouter des nœuds au cluster existant à l'aide de l'indicateur-join.

Le format pour cela est:

java -jar atomix-standalone-server.jar
  -address 127.0.0.1:8701 -join 127.0.0.1:8700

3. Travailler avec un client

Atomix prend en charge la création d'un client pour avoir un accès distant à son cluster, via l'APIAtomixClient.

Étant donné que les clients n'ont pas besoin d'être avec état,AtomixClient n'a pas de stockage. Nous devons simplement configurer le transport lors de la création du client car le transport sera utilisé pour communiquer avec un cluster.

Créons un client avec un transport:

AtomixClient client = AtomixClient.builder()
  .withTransport(new NettyTransport())
  .build();

Nous devons maintenant connecter le client au cluster.

Nous pouvons déclarer unList deAddress et passer leList comme argument à la méthodeconnect() du client:

client.connect(cluster)
  .thenRun(() -> {
      System.out.println("Client is connected to the cluster!");
  });

4. Gestion des ressources

La véritable puissance d'Atomix réside dans son ensemble complet d'API pour la création et la gestion de ressources distribuées. Resources are replicated and persisted in a cluster and are bolstered by a replicated state machine - géré par sa mise en œuvre sous-jacente du protocole de consensus sur les radeaux.

Les ressources distribuées peuvent être créées et gérées par l'une de ses méthodesget(). Nous pouvons créer une instance de ressource distribuée à partir deAtomixReplica.

Considérant quereplica est une instance deAtomixReplica, l'extrait de code pour créer une ressource de carte distribuée et lui attribuer une valeur:

replica.getMap("map")
  .thenCompose(m -> m.put("bar", "Hello world!"))
  .thenRun(() -> System.out.println("Value is set in Distributed Map"))
  .join();

Ici, la méthodejoin() bloquera le programme jusqu'à ce que la ressource soit créée et que sa valeur lui soit définie. Nous pouvons obtenir le même objet en utilisantAtomixClient et récupérer la valeur avec la méthodeget(“bar”).

On peut utiliser la méthodeget() à la fin pour attendre le résultat:

String value = client.getMap("map"))
  .thenCompose(m -> m.get("bar"))
  .thenApply(a -> (String) a)
  .get();

5. Cohérence et tolérance aux pannes

Atomix est utilisé pour les ensembles de données critiques à petite échelle pour lesquels la cohérence est beaucoup plus préoccupante que la disponibilité.

It provides strong configurable consistency through linearizability for both reads and writes. En matière de linéarisation, une fois qu'une écriture est validée, tous les clients sont assurés de connaître l'état résultant.

La cohérence dans le cluster d’Atomix est garantie par l’algorithme de consensus Raft sous-jacent dans lequel un chef élu aura toutes les écritures qui ont réussi auparavant.

Toutes les nouvelles écritures passent par le responsable du cluster et sont répliquées de manière synchrone sur la majorité du serveur avant la fin.

Pour maintenir la tolérance aux pannes,majority server of the cluster needs to be alive. En cas d'échec du nombre minoritaire de noeuds, les noeuds seront marqués comme inactifs et seront remplacés par des noeuds passifs ou de secours.

En cas de défaillance du chef, les serveurs restants du cluster entament une nouvelle élection. Pendant ce temps, le cluster sera indisponible.

En cas de partition, si un chef se trouve du côté non quorum de la partition, il se retire et un nouveau chef est élu avec un quorum.

Et, si le chef est du côté de la majorité, cela continuera sans changement. Lorsque la partition est résolue, les nœuds du côté non quorum rejoignent le quorum et mettent à jour leur journal en conséquence.

6. Conclusion

À l'instar de ZooKeeper, Atomix fournit un ensemble robuste de bibliothèques permettant de traiter les problèmes informatiques distribués.

Et, comme toujours, le code source complet de cette tâche est disponibleover on GitHub.