Apacheキュレーター入門

Apache Curatorの概要

1. 前書き

Apache Curatorは、分散アプリケーションで人気のある調整サービスであるApache ZookeeperのJavaクライアントです。

このチュートリアルでは、キュレーターが提供する最も関連性の高い機能のいくつかを紹介します。

  • 接続管理-接続の管理とポリシーの再試行

  • 非同期–非同期機能とJava 8ラムダの使用を追加することにより、既存のクライアントを強化します

  • 構成管理–システムの構成を一元化する

  • 強く型付けされたモデル–型付けされたモデルでの作業

  • レシピ–リーダー選挙、分散ロックまたはカウンターの実装

2. 前提条件

まず、Apache Zookeeperとその機能を簡単に確認することをお勧めします。

このチュートリアルでは、127.0.0.1:2181で実行されているスタンドアロンのZookeeperインスタンスがすでに存在することを前提としています。始めたばかりの場合は、インストールして実行する方法に関するhere areの説明。

まず、curator-x-asyncの依存関係をpom.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は、現在まだベータ版です。

そのため、この記事では、代わりに現在最新の安定したZookeeper 3.4.11を使用します。

したがって、Zookeeperの依存関係を除外し、pom.xmlthe dependency for our Zookeeper versionを追加する必要があります。


    org.apache.zookeeper
    zookeeper
    3.4.11

互換性の詳細については、this linkを参照してください。

3. 接続管理

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

このツールは、再試行ポリシーを使用してZookeeperへの接続を構築するファクトリーを提供します。

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

この簡単な例では、3回再試行し、接続に問題が発生した場合に再試行の間に100ミリ秒待機します。

CuratorFrameworkクライアントを使用してZookeeperに接続すると、パスを参照し、データを取得/設定し、基本的にサーバーと対話できるようになります。

4. 非同期

the CompletionStage Java 8 APIを使用するThe Curator Async module wraps the above CuratorFramework client to provide non-blocking capabilities

非同期ラッパーを使用した前の例がどのように見えるかを見てみましょう。

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

現在、checkExists()操作は非同期モードで機能し、メインスレッドをブロックしません。 代わりに、CompletionStage APIを使用するthenAcceptAsync()メソッドを使用して、アクションを次々にチェーンすることもできます。

5. 構成管理

分散環境で最も一般的な課題の1つは、多くのアプリケーション間で共有構成を管理することです。 We can use Zookeeper as a data store where to keep our configuration.

ApacheCuratorを使用してデータを取得および設定する例を見てみましょう。

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

この例では、ノードパスを作成し、Zookeeperにデータを設定してから、値が同じであることを確認して回復します。 keyフィールドは、/config/dev/my_keyのようなノードパスにすることができます。

5.1. ウォッチャー

Zookeeperのもう1つの興味深い機能は、キーまたはノードを監視する機能です。 It allows us to listen to changes in the configuration and update our applications without needing to redeploy

ウォッチャーを使用した場合の上記の例を見てみましょう。

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

ウォッチャーを構成し、データを設定してから、監視対象イベントがトリガーされたことを確認します。 1つのノードまたはノードのセットを一度に監視できます。

6. 強く型付けされたモデル

Zookeeperは主にバイト配列で動作するため、データをシリアル化および逆シリアル化する必要があります。 これにより、任意のシリアル化可能なインスタンスを操作する柔軟性が得られますが、維持するのが難しい場合があります。

ここで役立つように、キュレーターはdelegates the serialization/deserialization and allows us to work with our types directlyであるtyped modelsの概念を追加します。 それがどのように機能するか見てみましょう。

まず、シリアライザーフレームワークが必要です。 キュレーターはJackson実装の使用を推奨しているので、pom.xmlthe Jackson dependencyを追加しましょう。


    com.fasterxml.jackson.core
    jackson-databind
    2.9.4

それでは、カスタムクラスHostConfigを永続化してみましょう。

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

    // getters and setters
}

HostConfigクラスからパスへのモデル仕様マッピングを提供し、ApacheCuratorによって提供されるモデル化されたフレームワークラッパーを使用する必要があります。

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

パス/config/devを読み取るときにwhenComplete()メソッドは、ZookeeperにHostConfigインスタンスを返します。

7. レシピ

Zookeeperは、high-level solutions or recipes such as leader election, distributed locks or shared counters.を実装するためにthis guidelineを提供します

Apache Curatorは、これらのレシピのほとんどの実装を提供します。 完全なリストを表示するには、the Curator Recipes documentationにアクセスしてください。

これらのレシピはすべて、個別のモジュールで利用できます。


    org.apache.curator
    curator-recipes
    4.0.1

すぐに始めて、いくつかの簡単な例でこれらを理解し始めましょう。

7.1. リーダー選出

分散環境では、複雑なジョブを調整するために1つのマスターノードまたはリーダーノードが必要になる場合があります。

Curatorでのthe Leader Election recipeの使用法は次のようになります。

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

リーダーセレクターを起動すると、ノードはパス/mutex/select/leader/for/job/A内のメンバーグループに参加します。 ノードがリーダーになると、takeLeadershipメソッドが呼び出され、リーダーとしてジョブを再開できます。

7.2. 共有ロック

The Shared Lock recipeは、完全に分散されたロックを持つことについてです。

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

sharedLock.acquire();

// do process A

sharedLock.release();

ロックを取得すると、Zookeeperは、同じロックを同時に取得する他のアプリケーションがないことを確認します。

7.3. カウンター

The Counters recipeは、すべてのクライアント間で共有されるIntegerを調整します。

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

この例では、ZookeeperはInteger値をパス/counters/Aに格納し、パスがまだ作成されていない場合は値を0に初期化します。

8. 結論

この記事では、Apache Curatorを使用してApache Zookeeperに接続し、その主要機能を活用する方法を説明しました。

また、キュレーターでいくつかの主要なレシピを紹介しました。

いつものように、ソースはover on GitHubで見つけることができます。