Въведение в Apache Curator

1. Въведение

Apache Curator е Java клиент за Apache Zookeeper, популярната координационна услуга за разпределени приложения.

В този урок ще представим някои от най-подходящите функции, предоставени от Куратор:

  • Управление на връзките - управление на връзки и правила за нов опит
  • Async - подобряване на съществуващия клиент чрез добавяне на асинхронни възможности и използването на Java 8 lambdas
  • Управление на конфигурацията - има централизирана конфигурация за системата
  • Силно типизирани модели - работа с типизирани модели
  • Рецепти - изпълнение на избори за лидер, разпределени брави или гишета

2. Предпоставки

Като начало се препоръчва да разгледате набързо Apache Zookeeper и неговите функции.

За този урок предполагаме, че вече има самостоятелен екземпляр на Zookeeper, работещ на 127.0.0.1:2181 ; ето инструкции как да го инсталирате и стартирате, ако тепърва започвате.

Първо ще трябва да добавим зависимостта curator-x-async към нашия pom.xml :

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

Последната версия на Apache Curator 4.XX има твърда зависимост от Zookeeper 3.5.X, който все още е в бета версия.

И така, в тази статия вместо това ще използваме най-новия стабилен Zookeeper 3.4.11.

Затова трябва да изключим зависимостта на Zookeeper и да добавим зависимостта за нашата версия на Zookeeper към нашия pom.xml :

 org.apache.zookeeper zookeeper 3.4.11 

За повече информация относно съвместимостта вижте тази връзка.

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 ms между опитите в случай на проблеми със свързаността.

След като се свържем със Zookeeper с помощта на клиента CuratorFramework , вече можем да разглеждаме пътища, да получаваме / задаваме данни и по същество да взаимодействаме със сървъра.

4. Асинхронизиране

Модулът Curator Async обгръща горепосочения клиент CuratorFramework, за да осигури неблокиращи възможности, използвайки API на CompletionStage Java 8.

Нека да видим как изглежда предишният пример с помощта на обвивката 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 и след това го възстановяваме, като проверяваме дали стойността е същата. В ключов областта може да бъде път възел като / довереник / сътрудничество / 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 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 работи предимно с байтови масиви, така че трябва да сериализираме и десериализираме данните си. Това ни позволява известна гъвкавост при работа с всеки сериализуем екземпляр, но може да бъде трудно да се поддържа.

За да помогне тук, Кураторът добавя концепцията за типизирани модели, която делегира сериализацията / десериализацията и ни позволява да работим директно с нашите типове . Нека да видим как работи това.

Първо, имаме нужда от рамка за сериализатор. Кураторът препоръчва използването на изпълнението на Джаксън, така че нека добавим зависимостта Джаксън към нашия 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. 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

В тази статия видяхме как да използваме Apache Curator, за да се свържем с Apache Zookeeper и да се възползваме от основните му характеристики.

Представихме и няколко от основните рецепти в Куратор.

Както обикновено, източници могат да бъдат намерени в GitHub.