«1. Введение

Apache Ignite — это распределенная платформа с открытым исходным кодом, ориентированная на память. Мы можем использовать его как базу данных, систему кэширования или для обработки данных в памяти.

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

2. Установка и настройка

Для начала посетите начальную страницу, чтобы ознакомиться с инструкциями по начальной настройке и установке.

Зависимости Maven для приложения, которое мы собираемся построить:

<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-core</artifactId>
    <version>${ignite.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-indexing</artifactId>
    <version>${ignite.version}</version>
</dependency>

ignite-core — единственная обязательная зависимость для проекта. Поскольку мы также хотим взаимодействовать с SQL, зажигание-индексирование также здесь. ${ignite.version} — это последняя версия Apache Ignite.

В качестве последнего шага мы запускаем узел Ignite:

Ignite node started OK (id=53c77dea)
Topology snapshot [ver=1, servers=1, clients=0, CPUs=4, offheap=1.2GB, heap=1.0GB]
Data Regions Configured:
^-- default [initSize=256.0 MiB, maxSize=1.2 GiB, persistenceEnabled=false]

Вывод консоли выше показывает, что мы готовы к работе.

3. Архитектура памяти

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

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

Устойчивая архитектура памяти разбивается на блоки фиксированного размера, называемые страницами. Страницы хранятся вне кучи Java и организованы в оперативной памяти. Он имеет уникальный идентификатор: FullPageId.

Страницы взаимодействуют с памятью с помощью абстракции PageMemory.

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

4. Страницы памяти

Страница может находиться в следующих состояниях:

    Unloaded — в память не загружен буфер страниц Clear — буфер страниц загружен и синхронизирован с данными на диске Durty — «Буфер страницы содержит данные, отличные от данных на диске. Грязь в контрольной точке — другая модификация начинается до того, как первая сохраняется на диске. Здесь начинается контрольная точка, и PageMemory хранит два буфера памяти для каждой страницы.

Постоянная память выделяет локальный сегмент памяти, называемый областью данных. По умолчанию он имеет емкость 20% памяти кластера. Конфигурация нескольких регионов позволяет хранить полезные данные в памяти.

Максимальная емкость региона — это сегмент памяти. Это физическая память или непрерывный массив байтов.

Чтобы избежать фрагментации памяти, одна страница содержит несколько записей ключ-значение. Каждая новая запись будет добавляться на наиболее оптимальную страницу. Если размер пары ключ-значение превышает максимальную емкость страницы, Ignite сохраняет данные более чем на одной странице. Та же логика применима и к обновлению данных.

Индексы SQL и кэша хранятся в структурах, известных как деревья B+. Ключи кэша упорядочены по их значениям ключей.

5. Жизненный цикл

Каждый узел Ignite работает на одном экземпляре JVM. Однако можно настроить несколько узлов Ignite, работающих в одном процессе JVM.

Давайте рассмотрим типы событий жизненного цикла:

    BEFORE_NODE_START — до запуска узла Ignite AFTER_NODE_START — срабатывает сразу после запуска узла Ignite BEFORE_NODE_STOP — до инициации остановки узла AFTER_NODE_STOP — после запуска узла Ignite узел останавливается

Чтобы запустить узел Ignite по умолчанию:

Ignite ignite = Ignition.start();

Или из файла конфигурации:

Ignite ignite = Ignition.start("config/example-cache.xml");

В случае, если нам нужно больше контроля над процессом инициализации, есть другой способ с помощью LifecycleBean interface:

public class CustomLifecycleBean implements LifecycleBean {
 
    @Override
    public void onLifecycleEvent(LifecycleEventType lifecycleEventType) 
      throws IgniteException {
 
        if(lifecycleEventType == LifecycleEventType.AFTER_NODE_START) {
            // ...
        }
    }
}

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

Для этого мы передаем экземпляр конфигурации с CustomLifecycleBean в метод запуска:

IgniteConfiguration configuration = new IgniteConfiguration();
configuration.setLifecycleBeans(new CustomLifecycleBean());
Ignite ignite = Ignition.start(configuration);

6. Сетка данных в памяти

Сетка данных Ignite — это распределенное хранилище данных типа «ключ-значение», очень знакомое секционированный HashMap. Он масштабируется по горизонтали. Это означает, что мы добавляем больше узлов кластера, больше данных кэшируется или хранится в памяти.

«Это может обеспечить значительное повышение производительности стороннего программного обеспечения, такого как NoSql, базы данных RDMS в качестве дополнительного уровня для кэширования.

6.1. Поддержка кэширования

API доступа к данным основан на спецификации JCache JSR 107.

В качестве примера давайте создадим кеш, используя конфигурацию шаблона:

IgniteCache<Employee, Integer> cache = ignite.getOrCreateCache(
  "baeldingCache");

Давайте посмотрим, что здесь происходит, для более подробной информации. Сначала Ignite находит область памяти, в которой хранится кеш.

Затем страница индекса дерева B+ будет расположена на основе хэш-кода ключа. Если индекс существует, будет найдена страница данных соответствующего ключа.

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

Далее добавим несколько объектов Employee:

cache.put(1, new Employee(1, "John", true));
cache.put(2, new Employee(2, "Anna", false));
cache.put(3, new Employee(3, "George", true));

Опять же, долговременная память будет искать область памяти, к которой принадлежит кэш. На основе ключа кэша страница индекса будет расположена в древовидной структуре B+.

Если индексная страница не существует, запрашивается новая и добавляется в дерево.

Далее страница данных назначается странице индекса.

Чтобы прочитать сотрудника из кеша, мы просто используем значение ключа:

Employee employee = cache.get(1);

6.2. Поддержка потоковой передачи

Потоковая передача данных в памяти обеспечивает альтернативный подход для приложений обработки данных на основе дисков и файловой системы. Streaming API разбивает поток данных с высокой нагрузкой на несколько этапов и направляет их для обработки.

Мы можем изменить наш пример и передать данные из файла. Во-первых, мы определяем стример данных:

IgniteDataStreamer<Integer, Employee> streamer = ignite
  .dataStreamer(cache.getName());

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

streamer.receiver(StreamTransformer.from((e, arg) -> {
    Employee employee = e.getValue();
    employee.setEmployed(true);
    e.setValue(employee);
    return employee;
}));

В качестве последнего шага мы перебираем строки файла employee.txt. и преобразовать их в объекты Java:

Path path = Paths.get(IgniteStream.class.getResource("employees.txt")
  .toURI());
Gson gson = new Gson();
Files.lines(path)
  .forEach(l -> streamer.addData(
    employee.getId(), 
    gson.fromJson(l, Employee.class)));

С помощью streamer.addData() поместите объекты сотрудников в поток.

7. Поддержка SQL

Платформа предоставляет ориентированную на память отказоустойчивую базу данных SQL.

Мы можем подключиться либо с помощью чистого SQL API, либо с помощью JDBC. Синтаксис SQL здесь ANSI-99, поэтому поддерживаются все стандартные функции агрегации в запросах, операции языка DML, DDL.

7.1. JDBC

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

Для этого мы регистрируем драйвер JDBC и на следующем шаге открываем соединение:

Class.forName("org.apache.ignite.IgniteJdbcThinDriver");
Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");

С помощью стандартной DDL-команды заполняем таблицу Employee:

sql.executeUpdate("CREATE TABLE Employee (" +
  " id LONG PRIMARY KEY, name VARCHAR, isEmployed tinyint(1)) " +
  " WITH \"template=replicated\"");

После С помощью ключевого слова мы можем установить шаблон конфигурации кеша. Здесь мы используем REPLICATED. По умолчанию режим шаблона PARTITIONED. Чтобы указать количество копий данных, мы также можем указать здесь параметр BACKUPS, который по умолчанию равен 0.

Затем добавим некоторые данные с помощью инструкции INSERT DML:

PreparedStatement sql = conn.prepareStatement(
  "INSERT INTO Employee (id, name, isEmployed) VALUES (?, ?, ?)");

sql.setLong(1, 1);
sql.setString(2, "James");
sql.setBoolean(3, true);
sql.executeUpdate();

// add the rest

После этого мы выбираем записи:

ResultSet rs 
  = sql.executeQuery("SELECT e.name, e.isEmployed " 
    + " FROM Employee e " 
    + " WHERE e.isEmployed = TRUE ")

7.2. Запрос к объектам

Также возможно выполнить запрос к объектам Java, хранящимся в кэше. Ignite рассматривает объект Java как отдельную запись SQL:

IgniteCache<Integer, Employee> cache = ignite.cache("baeldungCache");

SqlFieldsQuery sql = new SqlFieldsQuery(
  "select name from Employee where isEmployed = 'true'");

QueryCursor<List<?>> cursor = cache.query(sql);

for (List<?> row : cursor) {
    // do something with the row
}

8. Резюме

В этом руководстве мы кратко рассмотрели проект Apache Ignite. В этом руководстве подчеркиваются преимущества платформы по сравнению с другими аналогичными продуктами, такие как прирост производительности, долговечность, облегченные API.

В результате мы узнали, как использовать язык SQL и Java API для хранения, извлечения и потоковой передачи данных внутри персистентной или in-memory сетки.

Как обычно, полный код этой статьи доступен на GitHub.