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

Руководство по Apache Ignite

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

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. Архитектура памяти

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

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

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

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

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

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 :

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 data grid — распределенное хранилище ключей-значений , очень знакомое с секционированным 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\"");

После ключевого слова WITH мы можем установить шаблон конфигурации кеша. Здесь мы используем 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("foreachCache");

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

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