Введение в Apache Curator

Введение в Apache Curator

1. Вступление

Apache Curator - это клиент Java дляApache Zookeeper, популярной службы координации распределенных приложений.

В этом руководстве мы познакомим вас с некоторыми из наиболее важных функций, предоставляемых Curator:

  • Управление соединениями - управление соединениями и политики повторных попыток

  • Async - расширение существующего клиента за счет добавления возможностей асинхронности и использования лямбда-выражений Java 8

  • Управление конфигурацией - наличие централизованной конфигурации для системы

  • Сильно типизированные модели - работа с типизированными моделями

  • Рецепты - реализация выборов лидера, распределенных замков или счетчиков

2. Предпосылки

Для начала рекомендуется взглянуть наApache Zookeeper и его функции.

В этом руководстве мы предполагаем, что уже существует автономный экземпляр Zookeeper, работающий на127.0.0.1:2181; 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 и добавитьthe dependency for our Zookeeper version к нашемуpom.xml:


    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 мс между попытками в случае проблем с подключением.

После подключения к Zookeeper с помощью клиентаCuratorFramework теперь мы можем просматривать пути, получать / устанавливать данные и по существу взаимодействовать с сервером.

4. асинхронный

The Curator Async module wraps the above CuratorFramework client to provide non-blocking capabilities с использованиемthe CompletionStage Java 8 API.

Давайте посмотрим, как выглядит предыдущий пример с использованием оболочки 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());

Теперь операцияcheckExists() работает в асинхронном режиме, не блокируя основной поток. Мы также можем связать действия одно за другим, используя вместо этого методthenAcceptAsync(), который используетCompletionStage API.

5. Управление конфигурацией

В распределенной среде одной из наиболее распространенных проблем является управление общей конфигурацией среди многих приложений. We can use Zookeeper as a data store where to keep our configuration.с

Давайте посмотрим на пример использования Apache Curator для получения и установки данных:

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 - возможность наблюдать за ключами или узлами. 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));

Мы настраиваем наблюдателя, устанавливаем данные и затем подтверждаем, что отслеживаемое событие было запущено. Мы можем наблюдать один узел или набор узлов одновременно.

6. Сильно типизированные модели

Zookeeper в основном работает с байтовыми массивами, поэтому нам нужно сериализовать и десериализовать наши данные. Это дает нам некоторую гибкость для работы с любым сериализуемым экземпляром, но его сложно поддерживать.

Чтобы помочь здесь, Куратор добавляет понятиеtyped models, котороеdelegates the serialization/deserialization and allows us to work with our types directly. Давайте посмотрим, как это работает.

Во-первых, нам нужен фреймворк сериализатора. Куратор рекомендует использовать реализацию Джексона, поэтому давайте добавимthe Jackson dependency к нашемуpom.xml:


    com.fasterxml.jackson.core
    jackson-databind
    2.9.4

Теперь давайте попробуем сохранить наш собственный классHostConfig:

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

    // getters and setters
}

Нам нужно предоставить отображение спецификации модели из классаHostConfig в путь и использовать смоделированную оболочку фреймворка, предоставленную 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);
     }
   });

МетодwhenComplete() при чтении пути/config/dev вернет экземплярHostConfig в Zookeeper.

7. Рецепты

Zookeeper предоставляетthis guideline для реализацииhigh-level solutions or recipes such as leader election, distributed locks or shared counters.

Apache Curator предоставляет реализацию для большинства из этих рецептов. Чтобы увидеть полный список, посетитеthe Curator Recipes documentation.

Все эти рецепты доступны в отдельном модуле:


    org.apache.curator
    curator-recipes
    4.0.1

Давайте сразу начнем понимать это с нескольких простых примеров.

7.1. Выборы лидера

В распределенной среде нам может понадобиться один главный или ведущий узел для координации сложной работы.

Вот как выглядит использованиеthe Leader Election recipe в 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();

Когда мы запускаем селектор лидера, наш узел присоединяется к группе участников в пределах пути/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.