Рубрики
Без рубрики

Руководство по бухгалтеру Apache

Узнайте, как использовать Apache BookKeeper.

Автор оригинала: Philippe Sevestre.

1. Обзор

В этой статье мы представим Бухгалтера , сервис , который реализует распределенную, отказоустойчивую систему хранения записей .

2. Что Такое Бухгалтер?

BookKeeper изначально был разработан Yahoo как подпроект ZooKeeper и в 2015 году стал проектом высшего уровня. По своей сути, BookKeeper стремится быть надежной и высокопроизводительной системой, которая хранит последовательности записей Журнала (он же Записи ) в структурах данных, называемых Бухгалтерскими книгами .

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

3. Концепции бухгалтера

3.1. Записи в Журнале

Запись в журнале содержит неделимую единицу данных, которую клиентское приложение хранит в Бухгалтерии или считывает из нее. При хранении в главной книге каждая запись содержит предоставленные данные и несколько полей метаданных.

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

Бухгалтер сам по себе не предлагает функций сериализации, поэтому клиенты должны разработать свой собственный метод преобразования конструкций более высокого уровня в/из байтовых массивов.

3.2. Бухгалтерские книги

Бухгалтерская книга-это базовая единица хранения, управляемая бухгалтером, в которой хранится упорядоченная последовательность записей журнала. Как упоминалось ранее, гроссбухи имеют семантику только для добавления, что означает, что записи не могут быть изменены после добавления в них.

Кроме того, как только клиент прекращает запись в бухгалтерскую книгу и закрывает ее, Бухгалтер запечатывает ее, и мы больше не можем добавлять в нее данные, даже в более позднее время . Это важный момент, который следует иметь в виду при разработке приложения вокруг бухгалтера. Бухгалтерские книги не являются хорошим кандидатом для непосредственной реализации конструкций более высокого уровня , таких как очередь. Вместо этого мы видим, что бухгалтерские книги чаще используются для создания более базовых структур данных, поддерживающих эти концепции более высокого уровня.

Например, Распределенный журнал проекта Apache использует бухгалтерские книги в качестве сегментов журнала. Эти сегменты объединяются в распределенные журналы, но базовые бухгалтерские книги прозрачны для обычных пользователей.

Бухгалтер обеспечивает устойчивость книги за счет репликации записей журнала на нескольких экземплярах сервера. Три параметра определяют, сколько серверов и копий хранится:

  • Размер ансамбля: количество серверов, используемых для записи данных главной книги.
  • Размер кворума записи: количество серверов, используемых для репликации данной записи журнала.
  • Размер кворума Ack: количество серверов, которые должны подтвердить данную операцию записи записи в журнал

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

В дополнение к своим внутренним метаданным, BookKeeper также поддерживает добавление пользовательских метаданных в книгу. Это карта пар ключ/значение, которые клиенты передают во время создания, а бухгалтер хранит в ZooKeeper наряду со своими собственными.

3.3. Букмекеры

Букмекеры-это серверы, на которых хранятся одна или несколько бухгалтерских книг. Кластер бухгалтеров состоит из нескольких букмекеров, работающих в данной среде и предоставляющих услуги клиентам по обычным соединениям TCP или TLS.

Букмекеры координируют действия с помощью кластерных сервисов, предоставляемых ZooKeeper. Это означает, что, если мы хотим создать полностью отказоустойчивую систему, нам нужны, по крайней мере, 3 экземпляра ZooKeeper и 3 экземпляра бухгалтера. Такая настройка позволит избежать потерь в случае сбоя любого отдельного экземпляра и по-прежнему сможет нормально работать, по крайней мере, для настройки книги по умолчанию: размер ансамбля 3 узла, кворум записи 2 узла и кворум ack 2 узла.

4. Локальная Настройка

Основные требования для ведения бухгалтерского учета на местном уровне довольно скромны. Во-первых, нам нужен запущенный экземпляр ZooKeeper, который обеспечивает хранение метаданных книги для бухгалтера. Затем мы внедряем букмекера, который предоставляет реальные услуги клиентам.

Хотя, безусловно, эти шаги можно выполнить вручную, здесь мы будем использовать файл docker-compose , в котором используются официальные образы Apache, чтобы упростить эту задачу:

$ cd 
$ docker-compose up

Этот docker-compose создает трех букмекеров и экземпляр ZooKeeper. Поскольку все букмекеры работают на одной и той же машине, это полезно только для целей тестирования. Официальная документация содержит необходимые шаги для настройки полностью отказоустойчивого кластера.

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

$ docker exec -it apache-bookkeeper_bookie_1 /opt/bookkeeper/bin/bookkeeper \
  shell listbookies -readwrite
ReadWrite Bookies :
192.168.99.101(192.168.99.101):4181
192.168.99.101(192.168.99.101):4182
192.168.99.101(192.168.99.101):3181

На выходе отображается список доступных букмекеров , состоящий из трех букмекеров. Пожалуйста, обратите внимание, что отображаемые IP-адреса будут меняться в зависимости от особенностей локальной установки Docker.

5. Использование API книги

API бухгалтерской книги-это самый простой способ взаимодействия с бухгалтером . Это позволяет нам напрямую взаимодействовать с объектами Ledger , но, с другой стороны, не поддерживает абстракции более высокого уровня, такие как потоки. Для этих случаев использования проект BookKeeper предлагает другую библиотеку, Распределенный журнал, которая поддерживает эти функции.

Использование API Ledger требует добавления зависимости bookkeeper-server в наш проект:


    org.apache.bookkeeper
    bookkeeper-server
    4.10.0

ПРИМЕЧАНИЕ: Как указано в документации, использование этой зависимости также будет включать зависимости для библиотек protobuf и guava . Если нашему проекту также понадобятся эти библиотеки, но в другой версии, чем те, которые использует бухгалтер, мы могли бы использовать альтернативную зависимость, которая оттеняет эти библиотеки :


    org.apache.bookkeeper
    bookkeeper-server-shaded
    4.10.0

5.1. Подключение к букмекерам

Класс Бухгалтер является основной точкой входа в API бухгалтерской книги , предоставляя несколько способов подключения к нашей службе бухгалтера. В простейшей форме все, что нам нужно сделать, это создать новый экземпляр этого класса, передав адрес одного из серверов ZooKeeper, используемых бухгалтером:

BookKeeper client = new BookKeeper("zookeeper-host:2131");

Здесь zookeeper-хост должен быть установлен на IP-адрес или имя хоста сервера ZooKeeper, на котором хранится конфигурация кластера бухгалтера. В нашем случае это обычно “локальный хост” или хост, на который указывает переменная среды DOCKER_HOST.

Если нам нужно больше контроля над несколькими параметрами, доступными для точной настройки нашего клиента, мы можем использовать Конфигурация клиента экземпляр и используйте его для создания нашего клиента:

ClientConfiguration cfg = new ClientConfiguration();
cfg.setMetadataServiceUri("zk+null://zookeeper-host:2131");

// ... set other properties
 
BookKeeper.forConfig(cfg).build();

5.2. Создание Главной книги

Как только у нас появится экземпляр БухгАлтера , создание новой книги будет простым:

LedgerHandle lh = bk.createLedger(BookKeeper.DigestType.MAC,"password".getBytes());

Здесь мы использовали самый простой вариант этого метода. Он создаст новую книгу с настройками по умолчанию, используя тип дайджеста MAC для обеспечения целостности записи.

Если мы хотим добавить пользовательские метаданные в нашу книгу, нам нужно использовать вариант, который принимает все параметры:

LedgerHandle lh = bk.createLedger(
  3,
  2,
  2,
  DigestType.MAC,
  "password".getBytes(),
  Collections.singletonMap("name", "my-ledger".getBytes()));

На этот раз мы использовали полную версию метода createLedger () . Три первых аргумента-это размер ансамбля, кворум записи и значения кворума ack соответственно. Далее, у нас те же параметры дайджеста, что и раньше. Наконец, мы передаем Карту с нашими пользовательскими метаданными.

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

bk.asyncCreateLedger(
  3,
  2,
  2,
  BookKeeper.DigestType.MAC, "passwd".getBytes(),
  (rc, lh, ctx) -> {
      // ... use lh to access ledger operations
  },
  null,
  Collections.emptyMap());

Более новые версии BookKeeper.6) также поддерживают API в стиле fluent и CompletableFuture для достижения той же цели:

CompletableFuture cf = bk.newCreateLedgerOp()
  .withDigestType(org.apache.bookkeeper.client.api.DigestType.MAC)
  .withPassword("password".getBytes())
  .execute();

Обратите внимание, что в этом случае мы получаем Дескриптор записи вместо LedgerHandle . Как мы увидим позже, мы можем использовать любой из них для доступа к вашей бухгалтерской книге в качестве LedgerHandle implements WriteHandle.

5.3. Запись Данных

Как только мы приобрели Ручку или Ручку записи , мы записываем данные в соответствующую книгу, используя один из вариантов метода append () . Давайте начнем с синхронного варианта:

for(int i = 0; i < MAX_MESSAGES; i++) {
    byte[] data = new String("message-" + i).getBytes();
    lh.append(data);
}

Здесь мы используем вариант, который принимает массив байт . API также поддерживает Netty ByteBuf и Java Nio ByteBuffer , которые позволяют лучше управлять памятью в критических по времени сценариях.

Для асинхронных операций API немного отличается в зависимости от конкретного типа дескриптора, который мы приобрели. WriteHandle использует CompletableFuture, в то время как LedgerHandle также поддерживает методы на основе обратного вызова:

// Available in WriteHandle and LedgerHandle
CompletableFuture f = lh.appendAsync(data);

// Available only in LedgerHandle
lh.asyncAddEntry(
  data,
  (rc,ledgerHandle,entryId,ctx) -> {
      // ... callback logic omitted
  },
  null);

Какой из них выбрать, в значительной степени является личным выбором, но в целом использование API на основе CompletableFuture , как правило, легче читается. Кроме того, есть побочное преимущество в том, что мы можем создать Mono непосредственно из него, что облегчает интеграцию бухгалтера в реактивные приложения.

5.4. Считывание Данных

Чтение данных из бухгалтерской книги работает аналогично записи. Во-первых, мы используем наш Бухгалтер экземпляр для создания LedgerHandle :

LedgerHandle lh = bk.openLedger(
  ledgerId, 
  BookKeeper.DigestType.MAC,
  ledgerPassword);

За исключением параметра ledgerId , который мы рассмотрим позже, этот код очень похож на метод createLedger () , который мы видели ранее. Однако есть важное различие; этот метод возвращает экземпляр LedgerHandle только для чтения . Если мы попытаемся использовать любой из доступных методов append () , все, что мы получим, – это исключение.

В качестве альтернативы, более безопасным способом является использование API в стиле fluent:

ReadHandle rh = bk.newOpenLedgerOp()
  .withLedgerId(ledgerId)
  .withDigestType(DigestType.MAC)
  .withPassword("password".getBytes())
  .execute()
  .get();

Ручка чтения имеет необходимые методы для чтения данных из вашей книги:

long lastId = lh.readLastConfirmed();
rh.read(0, lastId).forEach((entry) -> {
    // ... do something 
});

Здесь мы просто запросили все доступные данные в этой книге, используя синхронный вариант чтение . Как и ожидалось, существует также асинхронный вариант:

rh.readAsync(0, lastId).thenAccept((entries) -> {
    entries.forEach((entry) -> {
        // ... process entry
    });
});

Если мы решим использовать более старый метод open Ledger () , мы найдем дополнительные методы, которые поддерживают стиль обратного вызова для асинхронных методов:

lh.asyncReadEntries(
  0,
  lastId,
  (rc,lh,entries,ctx) -> {
      while(entries.hasMoreElements()) {
          LedgerEntry e = ee.nextElement();
      }
  },
  null);

5.5. Ведение Бухгалтерских книг

Ранее мы видели, что нам нужен идентификатор книги, чтобы открыть и прочитать ее данные. Итак, как нам его получить? Одним из способов является использование интерфейса Менеджер бухгалтерской книги , доступ к которому мы можем получить из нашего БухгАлтера экземпляра . Этот интерфейс в основном имеет дело с метаданными главной книги, но также имеет метод asyncProcess Ledgers () . Используя этот метод – и некоторые вспомогательные формы параллельных примитивов – мы можем перечислить все доступные бухгалтерские книги:

public List listAllLedgers(BookKeeper bk) {
    List ledgers = Collections.synchronizedList(new ArrayList<>());
    CountDownLatch processDone = new CountDownLatch(1);

    bk.getLedgerManager()
      .asyncProcessLedgers(
        (ledgerId, cb) -> {
            ledgers.add(ledgerId);
            cb.processResult(BKException.Code.OK, null, null);
        }, 
        (rc, s, obj) -> {
            processDone.countDown();
        },
        null,
        BKException.Code.OK,
        BKException.Code.ReadException);
 
    try {
        processDone.await(1, TimeUnit.MINUTES);
        return ledgers;
    } catch (InterruptedException ie) {
        throw new RuntimeException(ie);
    }
}

Давайте переварим этот код, который немного длиннее, чем ожидалось для, казалось бы, тривиальной задачи. Метод asyncProcess Ledgers() требует двух обратных вызовов .

Первый из них собирает все идентификаторы бухгалтерских книг в списке. Мы используем здесь синхронизированный список, потому что этот обратный вызов может быть вызван из нескольких потоков. Помимо идентификатора книги, этот обратный вызов также получает параметр обратного вызова. Мы должны вызвать его метод processResult () , чтобы подтвердить, что мы обработали данные, и дать сигнал о том, что мы готовы получить дополнительные данные.

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

6. Заключение

В этой статье мы рассмотрели проект Apache BookKeeper, рассмотрев его основные концепции и используя его низкоуровневый API для доступа к бухгалтерским книгам и выполнения операций чтения/записи.

Как обычно, весь код доступен на GitHub .