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

Краткое руководство по Apache Geode

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

1. Обзор

Apache Geode — это распределенная сетка данных в памяти, поддерживающая кэширование и вычисление данных.

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

2. Настройка

Во-первых, нам нужно загрузить и установить Apache Geode и настроить среду gfsh . Для этого мы можем следовать инструкциям в официальном руководстве Geode .

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

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

Из нашего временного каталога нам нужно запустить экземпляр Locator :

gfsh> start locator --name=locator --bind-address=localhost

Локаторы отвечают за координацию между различными членами кластера Geode , которым мы можем дополнительно управлять через JMX.

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

gfsh> start server --name=server1 --server-port=0

Мы устанавливаем для параметра –server-port значение 0, чтобы Geode выбирал любой доступный порт. Хотя, если мы опустим его, сервер будет использовать порт по умолчанию 40404. Сервер — это настраиваемый член кластера , который работает как долгоживущий процесс и отвечает за управление регионами данных .

И, наконец, нам нужен регион :

gfsh> create region --name=foreach --type=REPLICATE

Регион — это , в конечном счете, место, где мы будем хранить наши данные.

2.2. Проверка

Давайте удостоверимся, что у нас все работает, прежде чем двигаться дальше.

Во-первых, давайте проверим, есть ли у нас наш Server и наш Locator :

gfsh> list members
Name | Id
------- | ----------------------------------------------------------
server1 | 192.168.0.105(server1:6119)<v1>:1024
locator | 127.0.0.1(locator:5996:locator)<ec><v0>:1024 [Coordinator]

И далее, что у нас есть наш регион :

gfsh> describe region --name=foreach
..........................................................
Name : foreach
Data Policy : replicate
Hosting Members : server1

Non-Default Attributes Shared By Hosting Members

Type | Name | Value
------ | ----------- | ---------------
Region | data-policy | REPLICATE
| size | 0
| scope | distributed-ack

Кроме того, у нас должно быть несколько каталогов в файловой системе в нашем временном каталоге с именами «locator» и «server1».

С этим результатом мы знаем, что готовы двигаться дальше.

3. Зависимость от Maven

Теперь, когда у нас есть запущенный Geode, давайте приступим к изучению клиентского кода.

Чтобы работать с Geode в нашем Java-коде, нам нужно добавить клиентскую библиотеку Apache Geode Java в наш pom :

<dependency>
<groupId>org.apache.geode</groupId>
<artifactId>geode-core</artifactId>
<version>1.6.0</version>
</dependency>

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

4. Простое хранение и поиск

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

Чтобы начать хранить данные в нашем регионе «foreach», давайте подключимся к нему с помощью локатора:

@Before
public void connect() {
this.cache = new ClientCacheFactory()
.addPoolLocator("localhost", 10334)
.create();
this.region = cache.<String, String>
createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)
.create("foreach");
}

4.1. Сохранение отдельных значений

Теперь мы можем просто хранить и извлекать данные в нашем регионе:

@Test
public void whenSendMessageToRegion_thenMessageSavedSuccessfully() {

this.region.put("A", "Hello");
this.region.put("B", "ForEach");

assertEquals("Hello", region.get("A"));
assertEquals("ForEach", region.get("B"));
}

4.2. Сохранение нескольких значений одновременно

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

@Test
public void whenPutMultipleValuesAtOnce_thenValuesSavedSuccessfully() {

Supplier<Stream<String>> keys = () -> Stream.of("A", "B", "C", "D", "E");
Map<String, String> values = keys.get()
.collect(Collectors.toMap(Function.identity(), String::toLowerCase));

this.region.putAll(values);

keys.get()
.forEach(k -> assertEquals(k.toLowerCase(), this.region.get(k)));
}

4.3. Сохранение пользовательских объектов

Строки полезны, но рано или поздно нам понадобится хранить пользовательские объекты.

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

public class CustomerKey implements Serializable {
private long id;
private String country;

// getters and setters
// equals and hashcode
}

И следующий тип значения:

public class Customer implements Serializable {
private CustomerKey key;
private String firstName;
private String lastName;
private Integer age;

// getters and setters
}

Есть несколько дополнительных шагов, чтобы сохранить их:

Во- первых, они должны реализовать Serializable . Хотя это не является строгим требованием, сделав их сериализуемыми, Geode может хранить их более надежно .

Во- вторых, они должны быть в пути к классам нашего приложения, а также в пути к классам нашего сервера Geode .

Чтобы получить их в пути к классам сервера, давайте упакуем их, скажем, с помощью mvn clean package .

И затем мы можем сослаться на полученный jar в новой команде запуска сервера :

gfsh> stop server --name=server1
gfsh> start server --name=server1 --classpath=../lib/apache-geode-1.0-SNAPSHOT.jar --server-port=0

Опять же, мы должны запускать эти команды из временного каталога.

Наконец, давайте создадим на сервере новый регион с именем «foreach-customers», используя ту же команду, которую мы использовали для создания региона «foreach»: ``

gfsh> create region --name=foreach-customers --type=REPLICATE

В коде мы будем обращаться к локатору, как и раньше, указав пользовательский тип:

@Before
public void connect() {
// ... connect through the locator
this.customerRegion = this.cache.<CustomerKey, Customer>
createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY)
.create("foreach-customers");
}

И тогда мы можем сохранить нашего клиента, как и раньше:

@Test
public void whenPutCustomKey_thenValuesSavedSuccessfully() {
CustomerKey key = new CustomerKey(123);
Customer customer = new Customer(key, "William", "Russell", 35);

this.customerRegion.put(key, customer);

Customer storedCustomer = this.customerRegion.get(key);
assertEquals("William", storedCustomer.getFirstName());
assertEquals("Russell", storedCustomer.getLastName());
}

5. Типы регионов

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

До сих пор мы использовали реплицированные регионы в памяти. Давайте посмотрим поближе.

5.1. Реплицированный регион

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

Из консоли gfsh в рабочем каталоге добавим в кластер еще один Сервер с именем server2 :

gfsh> start server --name=server2 --classpath=../lib/apache-geode-1.0-SNAPSHOT.jar --server-port=0

Помните, что когда мы создавали «foreach», мы использовали –type=REPLICATE . Из-за этого Geode автоматически реплицирует наши данные на новый сервер.

Давайте проверим это, остановив server1:

gfsh> stop server --name=server1

А затем давайте выполним быстрый запрос к региону «foreach».

Если данные были реплицированы успешно, мы получим результаты обратно:

gfsh> query --query='select e.key from /foreach.entries e'
Result : true
Limit : 100
Rows : 5

Result
------
C
B
A
E
D

Итак, похоже, репликация прошла успешно!

Добавление реплики в наш регион повышает доступность данных. А поскольку на запросы может отвечать несколько серверов, мы также получим более высокую пропускную способность при чтении.

Но что, если они оба разобьются? Поскольку это области в памяти, данные будут потеряны . Для этого мы можем вместо этого использовать --type=REPLICATE_PERSISTENT , который также сохраняет данные на диске во время репликации.

5.2. Разделенный регион

С большими наборами данных мы можем лучше масштабировать систему, настроив Geode для разделения региона на отдельные разделы или сегменты.

Давайте создадим один разделенный регион с именем «foreach-partitioned»:

gfsh> create region --name=foreach-partitioned --type=PARTITION

Добавьте некоторые данные:

gfsh> put --region=foreach-partitioned --key="1" --value="one"
gfsh> put --region=foreach-partitioned --key="2" --value="two"
gfsh> put --region=foreach-partitioned --key="3" --value="three"

И быстро проверить:

gfsh> query --query='select e.key, e.value from /foreach-partitioned.entries e'
Result : true
Limit : 100
Rows : 3

key | value
--- | -----
2 | two
1 | one
3 | three

Затем, чтобы убедиться, что данные были разделены, давайте снова остановим server1 и повторим запрос:

gfsh> stop server --name=server1
gfsh> query --query='select e.key, e.value from /foreach-partitioned.entries e'
Result : true
Limit : 100
Rows : 1

key | value
--- | -----
2 | two

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

Но что, если нам нужно и разделение, и избыточность? Geode также поддерживает ряд других типов . Удобны следующие три:

  • PARTITION_REDUNDANT разделяет и реплицирует наши данные между разными членами кластера.
  • PARTITION_PERSISTENT разделяет данные, как PARTITION , но на диск, и
  • PARTITION_REDUNDANT_PERSISTENT дает нам все три варианта поведения.

6. Язык объектных запросов

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

Для этого примера воспользуемся созданным ранее регионом «foreach-customer».

Если мы добавим еще пару клиентов:

Map<CustomerKey, Customer> data = new HashMap<>();
data.put(new CustomerKey(1), new Customer("Gheorge", "Manuc", 36));
data.put(new CustomerKey(2), new Customer("Allan", "McDowell", 43));
this.customerRegion.putAll(data);

Затем мы можем использовать QueryService для поиска клиентов, чье имя «Аллан»:

QueryService queryService = this.cache.getQueryService();
String query =
"select * from /foreach-customers c where c.firstName = 'Allan'";
SelectResults<Customer> results =
(SelectResults<Customer>) queryService.newQuery(query).execute();
assertEquals(1, results.size());

7. Функция

Одним из наиболее мощных понятий сеток данных в памяти является идея «переноса вычислений в данные».

Проще говоря, поскольку Geode — это чистая Java, нам легко не только отправлять данные, но и выполнять логику над этими данными.

Это может напомнить нам об идее расширений SQL, таких как PL-SQL или Transact-SQL.

7.1. Определение функции

Чтобы определить единицу работы для Geode, `мы реализуем интерфейс функции Geode.`

Например, давайте представим, что нам нужно изменить все имена клиентов на верхний регистр.

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

public class UpperCaseNames implements Function<Boolean> {
@Override
public void execute(FunctionContext<Boolean> context) {
RegionFunctionContext regionContext = (RegionFunctionContext) context;
Region<CustomerKey, Customer> region = regionContext.getDataSet();

for ( Map.Entry<CustomerKey, Customer> entry : region.entrySet() ) {
Customer customer = entry.getValue();
customer.setFirstName(customer.getFirstName().toUpperCase());
}
context.getResultSender().lastResult(true);
}

@Override
public String getId() {
return getClass().getName();
}
}

Обратите внимание, что getId должен возвращать уникальное значение, поэтому имя класса обычно является хорошим выбором.

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

А у Function гораздо больше возможностей, поэтому ознакомьтесь с официальным руководством , особенно с методом getResultSender .

7.2. Развертывание функции

Нам нужно сообщить Geode о нашей функции, чтобы иметь возможность запустить ее. Как и в случае с нашими пользовательскими типами данных, мы упакуем банку.

Но на этот раз мы можем просто использовать команду deploy :

gfsh> deploy --jar=./lib/apache-geode-1.0-SNAPSHOT.jar

7.3. Выполнение функции

Теперь мы можем выполнить функцию из приложения с помощью FunctionService:

@Test
public void whenExecuteUppercaseNames_thenCustomerNamesAreUppercased() {
Execution execution = FunctionService.onRegion(this.customerRegion);
execution.execute(UpperCaseNames.class.getName());
Customer customer = this.customerRegion.get(new CustomerKey(1));
assertEquals("GHEORGE", customer.getFirstName());
}

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

В этой статье мы изучили основные концепции экосистемы Apache Geode . Мы рассмотрели простые операции get и put со стандартными и пользовательскими типами, реплицированные и секционированные регионы, а также поддержку oql и функций.

И, как всегда, все эти примеры доступны на GitHub .