Перейти к основному содержимому

Знакомство с куратором Apache

· 6 мин. чтения

1. Введение

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

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

  • Управление подключениями — управление подключениями и политиками повторных попыток.
  • Асинхронный — улучшение существующего клиента за счет добавления асинхронных возможностей и использования лямбда-выражений Java 8.
  • Управление конфигурацией – наличие централизованной конфигурации для системы
  • Строго типизированные модели — работа с типизированными моделями
  • Рецепты — реализация выборов лидера, распределенные блокировки или счетчики

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

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

В этом руководстве мы предполагаем, что уже есть автономный экземпляр Zookeeper, работающий по адресу 127.0.0.1:2181 ; вот инструкции по установке и запуску, если вы только начинаете.

Во- первых, нам нужно добавить зависимость curator-x-async к нашему pom.xml :

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-async</artifactId>
<version>4.0.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>

Последняя версия Apache Curator 4.XX имеет жесткую зависимость от Zookeeper 3.5.X , которая сейчас находится в стадии бета-тестирования.

Итак, в этой статье мы будем использовать последнюю на данный момент стабильную версию Zookeeper 3.4.11 .

Поэтому нам нужно исключить зависимость Zookeeper и добавить зависимость для нашей версии Zookeeper в наш pom.xml :

<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
</dependency>

Для получения дополнительной информации о совместимости перейдите по этой ссылке .

3. Управление подключением

Основной вариант использования Apache Curator — подключение к работающему экземпляру Apache Zookeeper .

Инструмент предоставляет фабрику для создания подключений к 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. Асинхронный

Модуль Curator Async является оболочкой для вышеуказанного клиента CuratorFramework , чтобы обеспечить неблокирующие возможности с помощью 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() , который использует API CompletionStage .

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

В распределенной среде одной из наиболее распространенных проблем является управление общей конфигурацией многих приложений. Мы можем использовать Zookeeper в качестве хранилища данных, где хранится наша конфигурация.

Давайте посмотрим на пример использования 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, а затем восстанавливаем их, проверяя, что значение совпадает. Поле ключа может быть путем к узлу, например, /config/dev/my_key .

5.1. Наблюдатели

Еще одной интересной функцией Zookeeper является возможность просмотра ключей или узлов. Это позволяет нам прослушивать изменения в конфигурации и обновлять наши приложения без необходимости повторного развертывания .

Давайте посмотрим, как приведенный выше пример выглядит при использовании наблюдателей:

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

async.create().forPath(key);

List<String> 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 в основном работает с байтовыми массивами, поэтому нам нужно сериализовать и десериализовать наши данные. Это дает нам некоторую гибкость для работы с любым сериализуемым экземпляром, но его может быть сложно поддерживать.

Чтобы помочь здесь, Curator добавляет концепцию типизированных моделей, которая делегирует сериализацию/десериализацию и позволяет нам напрямую работать с нашими типами . Давайте посмотрим, как это работает.

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

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.0</version>
</dependency>

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

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

// getters and setters
}

Нам нужно обеспечить отображение спецификации модели из класса HostConfig в путь и использовать смоделированную оболочку фреймворка, предоставленную Apache Curator:

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

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

AsyncCuratorFramework async
= AsyncCuratorFramework.wrap(client);
ModeledFramework<HostConfig> 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 предоставляет это руководство для реализации высокоуровневых решений или рецептов, таких как выбор лидера, распределенные блокировки или общие счетчики.

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

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

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>

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

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

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

Вот так выглядит использование рецепта Выборы Лидера в Кураторе:

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. Общие замки

Рецепт Shared Lock заключается в том, чтобы иметь полностью распределенную блокировку:

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

sharedLock.acquire();

// do process A

sharedLock.release();

Когда мы получаем блокировку, Zookeeper гарантирует, что никакое другое приложение не получит такую же блокировку одновременно.

7.3. Счетчики

Рецепт Counters координирует общий 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 и использования его основных функций.

Мы также представили несколько основных рецептов в Кураторе.

Как обычно, исходники можно найти на GitHub .