Introduction à Apache Curator

Introduction à Apache Curator

1. introduction

Apache Curator est un client Java pourApache Zookeeper, le service de coordination populaire pour les applications distribuées.

Dans ce didacticiel, nous présenterons certaines des fonctionnalités les plus pertinentes fournies par Curator:

  • Gestion des connexions - gestion des connexions et des stratégies de nouvelle tentative

  • Async - améliore le client existant en ajoutant des fonctionnalités asynchrones et l'utilisation de Java 8 lambdas

  • Gestion de la configuration - configuration centralisée du système

  • Modèles fortement typés - travailler avec des modèles dactylographiés

  • Recettes - mise en œuvre de l'élection du chef, serrures ou compteurs distribués

2. Conditions préalables

Pour commencer, il est recommandé de jeter un coup d'œil rapide sur lesApache Zookeeper et ses fonctionnalités.

Pour ce didacticiel, nous supposons qu'il existe déjà une instance Zookeeper autonome exécutée sur127.0.0.1:2181; Instructions dehere are sur la façon de l'installer et de l'exécuter, si vous ne faites que commencer.

Tout d'abord, nous devons ajouter la dépendancecurator-x-async à nospom.xml:


    org.apache.curator
    curator-x-async
    4.0.1
    
        
            org.apache.zookeeper
            zookeeper
        
    

The latest version of Apache Curator 4.X.X has a hard dependency with Zookeeper 3.5.X qui est toujours en version bêta pour le moment.

Et donc, dans cet article, nous allons utiliser les derniersZookeeper 3.4.11 stables actuellement.

Nous devons donc exclure la dépendance Zookeeper et ajouterthe dependency for our Zookeeper version à nospom.xml:


    org.apache.zookeeper
    zookeeper
    3.4.11

Pour plus d'informations sur la compatibilité, reportez-vous àthis link.

3. Gestion des connexions

The basic use case of Apache Curator is connecting to a running Apache Zookeeper instance.

L'outil fournit une usine pour établir des connexions à Zookeeper à l'aide de stratégies de nouvelle tentative:

int sleepMsBetweenRetries = 100;
int maxRetries = 3;
RetryPolicy retryPolicy = new RetryNTimes(
  maxRetries, sleepMsBetweenRetries);

CuratorFramework client = CuratorFrameworkFactory
  .newClient("127.0.0.1:2181", retryPolicy);
client.start();

assertThat(client.checkExists().forPath("/")).isNotNull();

Dans cet exemple rapide, nous réessayerons 3 fois et attendrons 100 ms entre les tentatives en cas de problèmes de connectivité.

Une fois connecté à Zookeeper en utilisant le clientCuratorFramework, nous pouvons maintenant parcourir les chemins, obtenir / définir des données et essentiellement interagir avec le serveur.

4. Async

The Curator Async module wraps the above CuratorFramework client to provide non-blocking capabilities en utilisantthe CompletionStage Java 8 API.

Voyons à quoi ressemble l'exemple précédent en utilisant le wrapper Async:

int sleepMsBetweenRetries = 100;
int maxRetries = 3;
RetryPolicy retryPolicy
  = new RetryNTimes(maxRetries, sleepMsBetweenRetries);

CuratorFramework client = CuratorFrameworkFactory
  .newClient("127.0.0.1:2181", retryPolicy);

client.start();
AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);

AtomicBoolean exists = new AtomicBoolean(false);

async.checkExists()
  .forPath("/")
  .thenAcceptAsync(s -> exists.set(s != null));

await().until(() -> assertThat(exists.get()).isTrue());

Désormais, l'opérationcheckExists() fonctionne en mode asynchrone, sans bloquer le thread principal. Nous pouvons également enchaîner les actions les unes après les autres en utilisant la méthodethenAcceptAsync() à la place, qui utilise lesCompletionStage API.

5. Gestion de la configuration

Dans un environnement distribué, l'un des défis les plus courants consiste à gérer la configuration partagée entre de nombreuses applications. We can use Zookeeper as a data store where to keep our configuration.

Voyons un exemple d'utilisation d'Apache Curator pour obtenir et définir des données:

CuratorFramework client = newClient();
client.start();
AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);
String key = getKey();
String expected = "my_value";

client.create().forPath(key);

async.setData()
  .forPath(key, expected.getBytes());

AtomicBoolean isEquals = new AtomicBoolean();
async.getData()
  .forPath(key)
  .thenAccept(data -> isEquals.set(new String(data).equals(expected)));

await().until(() -> assertThat(isEquals.get()).isTrue());

Dans cet exemple, nous créons le chemin du nœud, définissons les données dans Zookeeper, puis nous le récupérons en vérifiant que la valeur est la même. Le champkey peut être un chemin de nœud comme/config/dev/my_key.

5.1. Observateurs

Une autre fonctionnalité intéressante de Zookeeper est la possibilité de regarder des clés ou des nœuds. It allows us to listen to changes in the configuration and update our applications without needing to redeploy.

Voyons à quoi ressemble l'exemple ci-dessus lorsque vous utilisez des observateurs:

CuratorFramework client = newClient()
client.start();
AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);
String key = getKey();
String expected = "my_value";

async.create().forPath(key);

List changes = new ArrayList<>();

async.watched()
  .getData()
  .forPath(key)
  .event()
  .thenAccept(watchedEvent -> {
    try {
        changes.add(new String(client.getData()
          .forPath(watchedEvent.getPath())));
    } catch (Exception e) {
        // fail ...
    }});

// Set data value for our key
async.setData()
  .forPath(key, expected.getBytes());

await()
  .until(() -> assertThat(changes.size()).isEqualTo(1));

Nous configurons l'observateur, définissons les données, puis confirmons le déclenchement de l'événement surveillé. Nous pouvons regarder un nœud ou un ensemble de nœuds à la fois.

6. Modèles fortement typés

Zookeeper fonctionne principalement avec des tableaux d'octets, nous devons donc sérialiser et désérialiser nos données. Cela nous permet une certaine flexibilité pour travailler avec n'importe quelle instance sérialisable, mais cela peut être difficile à maintenir.

Pour aider ici, Curator ajoute le concept detyped models quidelegates the serialization/deserialization and allows us to work with our types directly. Voyons comment cela fonctionne.

Premièrement, nous avons besoin d’un framework de sérialiseur. Curator recommande d’utiliser l’implémentation de Jackson, alors ajoutonsthe Jackson dependency à nospom.xml:


    com.fasterxml.jackson.core
    jackson-databind
    2.9.4

Maintenant, essayons de conserver notre classe personnaliséeHostConfig:

public class HostConfig {
    private String hostname;
    private int port;

    // getters and setters
}

Nous devons fournir le mappage de spécification de modèle de la classeHostConfig vers un chemin et utiliser le wrapper de framework modélisé fourni par Apache Curator:

ModelSpec mySpec = ModelSpec.builder(
  ZPath.parseWithIds("/config/dev"),
  JacksonModelSerializer.build(HostConfig.class))
  .build();

CuratorFramework client = newClient();
client.start();

AsyncCuratorFramework async
  = AsyncCuratorFramework.wrap(client);
ModeledFramework modeledClient
  = ModeledFramework.wrap(async, mySpec);

modeledClient.set(new HostConfig("host-name", 8080));

modeledClient.read()
  .whenComplete((value, e) -> {
     if (e != null) {
          fail("Cannot read host config", e);
     } else {
          assertThat(value).isNotNull();
          assertThat(value.getHostname()).isEqualTo("host-name");
          assertThat(value.getPort()).isEqualTo(8080);
     }
   });

La méthodewhenComplete() lors de la lecture du chemin/config/dev renverra l'instanceHostConfig dans Zookeeper.

7. Recettes

Zookeeper fournitthis guideline pour implémenterhigh-level solutions or recipes such as leader election, distributed locks or shared counters.

Apache Curator fournit une implémentation pour la plupart de ces recettes. Pour voir la liste complète, visitezthe Curator Recipes documentation.

Toutes ces recettes sont disponibles dans un module séparé:


    org.apache.curator
    curator-recipes
    4.0.1

Allons-y et commençons à les comprendre avec quelques exemples simples.

7.1. Élection du chef

Dans un environnement distribué, un nœud principal ou un nœud principal peut être nécessaire pour coordonner un travail complexe.

Voici à quoi ressemble l'utilisation dethe Leader Election recipe dans Curator:

CuratorFramework client = newClient();
client.start();
LeaderSelector leaderSelector = new LeaderSelector(client,
  "/mutex/select/leader/for/job/A",
  new LeaderSelectorListener() {
      @Override
      public void stateChanged(
        CuratorFramework client,
        ConnectionState newState) {
      }

      @Override
      public void takeLeadership(
        CuratorFramework client) throws Exception {
      }
  });

// join the members group
leaderSelector.start();

// wait until the job A is done among all members
leaderSelector.close();

Lorsque nous démarrons le sélecteur de leader, notre nœud rejoint un groupe de membres dans le chemin/mutex/select/leader/for/job/A. Une fois que notre nœud devient le leader, la méthodetakeLeadership sera invoquée, et nous, en tant que leaders, pouvons reprendre le travail.

7.2. Serrures partagées

The Shared Lock recipe consiste à avoir un verrou entièrement distribué:

CuratorFramework client = newClient();
client.start();
InterProcessSemaphoreMutex sharedLock = new InterProcessSemaphoreMutex(
  client, "/mutex/process/A");

sharedLock.acquire();

// do process A

sharedLock.release();

Lorsque nous acquérons le verrou, Zookeeper s'assure qu'aucune autre application n'acquiert le même verrou en même temps.

7.3. Compteurs

The Counters recipe coordonne unInteger partagé entre tous les clients:

CuratorFramework client = newClient();
client.start();

SharedCount counter = new SharedCount(client, "/counters/A", 0);
counter.start();

counter.setCount(counter.getCount() + 1);

assertThat(counter.getCount()).isEqualTo(1);

Dans cet exemple, Zookeeper stocke la valeurInteger dans le chemin/counters/A et initialise la valeur à0 si le chemin n'a pas encore été créé.

8. Conclusion

Dans cet article, nous avons vu comment utiliser Apache Curator pour se connecter à Apache Zookeeper et tirer parti de ses principales fonctionnalités.

Nous avons également présenté quelques-unes des principales recettes de Curator.

Comme d'habitude, les sources peuvent être trouvéesover on GitHub.