Einführung in Apache Curator

1. Einleitung

Apache Curator ist ein Java-Client für Apache Zookeeper, den beliebten Koordinierungsdienst für verteilte Anwendungen.

In diesem Tutorial stellen wir einige der wichtigsten Funktionen von Curator vor:

  • Verbindungsverwaltung - Verwalten von Verbindungen und Wiederholungsrichtlinien
  • Async - Verbesserung des vorhandenen Clients durch Hinzufügen von Async-Funktionen und Verwendung von Java 8-Lambdas
  • Konfigurationsmanagement - mit einer zentralisierten Konfiguration für das System
  • Stark typisierte Modelle - Arbeiten mit typisierten Modellen
  • Rezepte - Durchführung von Führerwahlen, verteilten Sperren oder Zählern

2. Voraussetzungen

Zunächst wird empfohlen, einen kurzen Blick auf den Apache Zookeeper und seine Funktionen zu werfen.

In diesem Tutorial wird davon ausgegangen, dass bereits eine eigenständige Zookeeper-Instanz auf 127.0.0.1:2181 ausgeführt wird . Hier finden Sie Anweisungen zur Installation und Ausführung, wenn Sie gerade erst anfangen.

Zuerst müssen wir die Abhängigkeit von curator-x-async zu unserer pom.xml hinzufügen :

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

Die neueste Version von Apache Curator 4.XX hat eine starke Abhängigkeit von Zookeeper 3.5.X, das sich derzeit noch in der Beta befindet.

In diesem Artikel verwenden wir stattdessen den aktuell stabilsten Zookeeper 3.4.11.

Daher müssen wir die Zookeeper-Abhängigkeit ausschließen und die Abhängigkeit für unsere Zookeeper-Version zu unserer pom.xml hinzufügen :

 org.apache.zookeeper zookeeper 3.4.11 

Weitere Informationen zur Kompatibilität finden Sie unter diesem Link.

3. Verbindungsverwaltung

Der grundlegende Anwendungsfall von Apache Curator besteht darin, eine Verbindung zu einer laufenden Apache Zookeeper-Instanz herzustellen .

Das Tool bietet eine Factory zum Herstellen von Verbindungen zu Zookeeper mithilfe von Wiederholungsrichtlinien:

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

In diesem kurzen Beispiel versuchen wir es dreimal und warten bei Verbindungsproblemen 100 ms zwischen den Wiederholungen.

Sobald über den CuratorFramework- Client eine Verbindung zu Zookeeper hergestellt wurde , können wir jetzt Pfade durchsuchen, Daten abrufen / festlegen und im Wesentlichen mit dem Server interagieren.

4. Async

Das Curator Async-Modul umschließt den oben genannten CuratorFramework- Client, um mithilfe der CompletionStage Java 8-API nicht blockierende Funktionen bereitzustellen .

Mal sehen, wie das vorherige Beispiel mit dem Async-Wrapper aussieht:

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

Jetzt arbeitet die checkExists () -Operation im asynchronen Modus und blockiert den Hauptthread nicht. Wir können Aktionen auch nacheinander mit der thenAcceptAsync () -Methode verketten , die die CompletionStage-API verwendet.

5. Konfigurationsverwaltung

In einer verteilten Umgebung besteht eine der häufigsten Herausforderungen darin, die gemeinsam genutzte Konfiguration für viele Anwendungen zu verwalten. Wir können Zookeeper als Datenspeicher verwenden, in dem unsere Konfiguration gespeichert wird.

Sehen wir uns ein Beispiel mit Apache Curator an, um Daten abzurufen und festzulegen:

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

In diesem Beispiel erstellen wir den Knotenpfad, legen die Daten in Zookeeper fest und stellen sie dann wieder her, um zu überprüfen, ob der Wert identisch ist. Das Schlüsselfeld könnte ein Knotenpfad wie / config / dev / my_key sein .

5.1. Beobachter

Ein weiteres interessantes Feature in Zookeeper ist die Möglichkeit, Schlüssel oder Knoten zu überwachen. Dadurch können wir Änderungen in der Konfiguration abhören und unsere Anwendungen aktualisieren, ohne sie erneut bereitstellen zu müssen .

Mal sehen, wie das obige Beispiel bei der Verwendung von Watchern aussieht:

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

Wir konfigurieren den Watcher, stellen die Daten ein und bestätigen dann, dass das beobachtete Ereignis ausgelöst wurde. Wir können einen Knoten oder eine Reihe von Knoten gleichzeitig beobachten.

6. Stark typisierte Modelle

Zookeeper arbeitet hauptsächlich mit Byte-Arrays, daher müssen wir unsere Daten serialisieren und deserialisieren. Dies ermöglicht uns eine gewisse Flexibilität bei der Arbeit mit jeder serialisierbaren Instanz, kann jedoch schwierig zu warten sein.

Um hier zu helfen, fügt Curator das Konzept typisierter Modelle hinzu, das die Serialisierung / Deserialisierung delegiert und es uns ermöglicht, direkt mit unseren Typen zu arbeiten . Mal sehen, wie das funktioniert.

Zunächst benötigen wir ein Serializer-Framework. Der Kurator empfiehlt die Verwendung der Jackson-Implementierung. Fügen wir also die Jackson-Abhängigkeit zu unserer pom.xml hinzu :

 com.fasterxml.jackson.core jackson-databind 2.9.4 

Versuchen wir nun, unsere benutzerdefinierte Klasse HostConfig beizubehalten :

public class HostConfig { private String hostname; private int port; // getters and setters }

Wir müssen die Modellspezifikationszuordnung von der HostConfig- Klasse zu einem Pfad bereitstellen und den von Apache Curator bereitgestellten modellierten Framework-Wrapper verwenden:

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

Die whenComplete () -Methode beim Lesen des Pfads / config / dev gibt die HostConfig- Instanz in Zookeeper zurück.

7. Recipes

Zookeeper provides this guideline to implement high-level solutions or recipes such as leader election, distributed locks or shared counters.

Apache Curator provides an implementation for most of these recipes. To see the full list, visit the Curator Recipes documentation.

All of these recipes are available in a separate module:

 org.apache.curator curator-recipes 4.0.1 

Let's jump right in and start understanding these with some simple examples.

7.1. Leader Election

In a distributed environment, we may need one master or leader node to coordinate a complex job.

This is how the usage of the Leader Election recipe in Curator looks like:

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

When we start the leader selector, our node joins a members group within the path /mutex/select/leader/for/job/A. Once our node becomes the leader, the takeLeadership method will be invoked, and we as leaders can resume the job.

7.2. Shared Locks

The Shared Lock recipe is about having a fully distributed lock:

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

When we acquire the lock, Zookeeper ensures that there's no other application acquiring the same lock at the same time.

7.3. Counters

The Counters recipe coordinates a shared Integer among all the 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);

In this example, Zookeeper stores the Integer value in the path /counters/A and initializes the value to 0 if the path has not been created yet.

8. Conclusion

In diesem Artikel haben wir gesehen, wie Sie mit Apache Curator eine Verbindung zu Apache Zookeeper herstellen und die Hauptfunktionen nutzen können.

Wir haben auch einige der Hauptrezepte in Curator vorgestellt.

Wie üblich finden Sie Quellen auf GitHub.