Hazelcast — это ведущее решение для сетки данных в памяти ( IMDG ). Основная идея IMDG заключается в распределении данных по многим узлам внутри кластера. Таким образом, это кажется идеальным решением для работы на облачной платформе, такой как Kubernetes , где вы можете легко увеличить или уменьшить количество запущенных экземпляров. Поскольку Hazelcast написан на Java, вы можете легко интегрировать его с вашим Java-приложением, используя стандартные библиотеки. Spring Boot также может упростить начало работы с Hazelcast . Вы также можете использовать неофициальную библиотеку, реализующую шаблон Spring Repositories для Hazelcast — Spring Data Hazelcast .
Основная цель этой статьи — продемонстрировать, как встроить Hazelcast в приложение Spring Boot и запустить его на Kubernetes в качестве кластера с несколькими экземплярами. Благодаря Spring Data Hazelcast нам не нужно вдаваться в детали типов данных Hazelcast. Хотя Spring Data Hazelcast не предоставляет много продвинутых функций, он очень хорош для начала.
Ищете что-то другое? Узнайте, как использовать встроенный Hazelcast в Kubernetes .
Архитектура
Мы работаем с несколькими экземплярами одного приложения Spring Boot в Kubernetes. Каждое приложение предоставляет порт 8080 для доступа по API HTTP и 5701 для обнаружения членов кластера Hazelcast. Экземпляры Hazelcast встроены в приложения Spring Boot. Мы создаем две службы на Kubernetes. Первый из них предназначен для доступа через HTTP API, а второй отвечает за включение обнаружения между экземплярами Hazelcast. HTTP API будет использоваться для выполнения некоторых тестовых запросов, которые добавляют данные в кластер и находят там данные. Давайте перейдем к реализации.
пример
Исходный код с примером приложения, как обычно, доступен на GitHub. Он доступен по адресу https://github.com/piomin/sample-hazelcast-spring-datagrid.git . Вам необходимо получить доступ к модулю employee-kubernetes-service .
зависимости
Интеграция между Spring и Hazelcast обеспечивается hazelcast-spring библиотекой. Версия библиотек Hazelcast связана с Spring Boot через управление зависимостями, поэтому нам просто нужно определить версию Spring Boot для новейшей стабильной версии 2.2.4.RELEASE. Текущая версия Hazelcast связана с этой версией Spring Boot 3.12.5. Чтобы включить обнаружение членов Hazelcast в Kubernetes, нам также необходимо включить эту hazelcast-kubernetes зависимость. Его версия не зависит от основных библиотек. Новая версия 2.0 предназначена для Hazelcast 4. Так как мы все еще используем Hazelcast 3, мы объявляем версию 1.5.2 о hazelcast-kubernetes. Мы также включили Spring Data Hazelcast и, опционально, Lombok для упрощения.
XML
xxxxxxxxxx
1
<parent>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-parent</artifactId>
4
<version>2.2.4.RELEASE</version>
5
</parent>
6
<dependencies>
7
<dependency>
8
<groupId>org.springframework.boot</groupId>
9
<artifactId>spring-boot-starter-web</artifactId>
10
</dependency>
11
<dependency>
12
<groupId>com.hazelcast</groupId>
13
<artifactId>spring-data-hazelcast</artifactId>
14
<version>2.2.2</version>
15
</dependency>
16
<dependency>
17
<groupId>com.hazelcast</groupId>
18
<artifactId>hazelcast-spring</artifactId>
19
</dependency>
20
<dependency>
21
<groupId>com.hazelcast</groupId>
22
<artifactId>hazelcast-client</artifactId>
23
</dependency>
24
<dependency>
25
<groupId>com.hazelcast</groupId>
26
<artifactId>hazelcast-kubernetes</artifactId>
27
<version>1.5.2</version>
28
</dependency>
29
<dependency>
30
<groupId>org.projectlombok</groupId>
31
<artifactId>lombok</artifactId>
32
</dependency>
33
</dependencies>
Включение Kubernetes Discovery
После включения необходимых зависимостей Hazelcast был включен для нашего приложения. Единственное, что нам нужно сделать, это включить обнаружение через Kubernetes. HazelcastInstance Боб уже доступен в контексте, так что мы можем изменить его конфигурацию путем определения com.hazelcast.config.Config боба. Нам нужно отключить многоадресное обнаружение, которое включено по умолчанию, и включить обнаружение Kubernetes в конфигурации сети, как показано ниже. Конфигурация Kubernetes требует задания целевого пространства имен для развертывания Hazelcast и его имени службы.
Джава
xxxxxxxxxx
1
2
Config config() {
3
Config config = new Config();
4
config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(false);
5
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
6
config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true)
7
.setProperty("namespace", "default")
8
.setProperty("service-name", "hazelcast-service");
9
return config;
10
}
Мы также должны определить сервис Kubernetes hazelcast-service на порту 5701. На него ссылаются при employee-service развертывании.
YAML
xxxxxxxxxx
1
apiVersionv1
2
kindService
3
metadata
4
namehazelcast-service
5
spec
6
selector
7
appemployee-service
8
ports
9
namehazelcast
10
port5701
11
typeLoadBalancer
Вот определение развертывания и обслуживания Kubernetes для нашего примера приложения. Мы устанавливаем три реплики для нашего развертывания. Мы также выставляем два порта вне контейнеров.
YAML
xxxxxxxxxx
1
apiVersionapps/v1
2
kindDeployment
3
metadata
4
nameemployee-service
5
labels
6
appemployee-service
7
spec
8
replicas3
9
selector
10
matchLabels
11
appemployee-service
12
template
13
metadata
14
labels
15
appemployee-service
16
spec
17
containers
18
nameemployee-service
19
imagepiomin/employee-service
20
ports
21
namehttp
22
containerPort8080
23
namemulticast
24
containerPort5701
25
---
26
apiVersionv1
27
kindService
28
metadata
29
nameemployee-service
30
labels
31
appemployee-service
32
spec
33
ports
34
port8080
35
protocolTCP
36
selector
37
appemployee-service
38
typeNodePort
Фактически это все, что нужно сделать для успешного запуска кластера Hazelcast в Кубернетесе. Прежде чем приступить к развертыванию, давайте взглянем на детали реализации приложения.
Реализация
Наше приложение очень простое. Он определяет один объект модели, который хранится в кластере Hazelcast. Такой класс должен иметь идентификатор — поле, помеченное Spring Data @Id, и должен реализовывать Seriazable интерфейс.
Джава
xxxxxxxxxx
1
2
3
4
5
public class Employee implements Serializable {
6
7
8
private Long id;
9
.Exclude
10
private Integer personId;
11
.Exclude
12
private String company;
13
.Exclude
14
private String position;
15
.Exclude
16
private int salary;
17
18
}
С помощью Spring Data Hazelcast мы можем определять репозитории без использования каких-либо запросов или специфичного для Hazelcast API-интерфейса для запросов. Мы используем известный шаблон именования методов, определенный Spring Data, для создания методов поиска, как показано ниже. Наш интерфейс хранилища должен расширяться HazelcastRepository.
Джава
xxxxxxxxxx
1
public interface EmployeeRepository extends HazelcastRepository<Employee, Long> {
2
3
Employee findByPersonId(Integer personId);
4
List<Employee> findByCompany(String company);
5
List<Employee> findByCompanyAndPosition(String company, String position);
6
List<Employee> findBySalaryGreaterThan(int salary);
7
8
}
Чтобы включить репозитории Spring Data Hazelcast, мы должны пометить основной класс или класс конфигурации с помощью @EnableHazelcastRepositories.
Джава
xxxxxxxxxx
1
2
3
public class EmployeeApplication {
4
5
public static void main(String[] args) {
6
SpringApplication.run(EmployeeApplication.class, args);
7
}
8
9
}
Наконец, вот реализация контроллера Spring. Это позволяет нам вызывать все методы поиска, определенные в репозитории, добавлять новый Employee объект в Hazelcast и удалять существующий.
Джава
xxxxxxxxxx
1
2
("/employees")
3
public class EmployeeController {
4
5
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
6
7
private EmployeeRepository repository;
8
9
EmployeeController(EmployeeRepository repository) {
10
this.repository = repository;
11
}
12
13
("/person/{id}")
14
public Employee findByPersonId(("id") Integer personId) {
15
logger.info("findByPersonId({})", personId);
16
return repository.findByPersonId(personId);
17
}
18
19
("/company/{company}")
20
public List<Employee> findByCompany(("company") String company) {
21
logger.info(String.format("findByCompany({})", company));
22
return repository.findByCompany(company);
23
}
24
25
("/company/{company}/position/{position}")
26
public List<Employee> findByCompanyAndPosition(("company") String company, ("position") String position) {
27
logger.info(String.format("findByCompany({}, {})", company, position));
28
return repository.findByCompanyAndPosition(company, position);
29
}
30
31
("/{id}")
32
public Employee findById(("id") Long id) {
33
logger.info("findById({})", id);
34
return repository.findById(id).get();
35
}
36
37
("/salary/{salary}")
38
public List<Employee> findBySalaryGreaterThan(("salary") int salary) {
39
logger.info(String.format("findBySalaryGreaterThan({})", salary));
40
return repository.findBySalaryGreaterThan(salary);
41
}
42
43
44
public Employee add( Employee emp) {
45
logger.info("add({})", emp);
46
return repository.save(emp);
47
}
48
49
("/{id}")
50
public void delete(("id") Long id) {
51
logger.info("delete({})", id);
52
repository.deleteById(id);
53
}
54
55
}
Бег на Миникубе
Мы протестируем наш пример приложения на Minikube.
Оболочка
xxxxxxxxxx
1
$ minikube start --vm-driver=virtualbox
Приложение настроено для работы со Skaffold и плагином Jib Maven. Я уже описал оба этих инструмента в одной из моих предыдущих статей. Они упрощают процесс сборки и развертывания в Minikube. Предполагая, что мы находимся в корневом каталоге нашего приложения, нам просто нужно выполнить следующую команду. Skaffold автоматически создает наше приложение с использованием Maven, создает образ Docker на основе настроек Maven, применяет файл развертывания из k8s каталога и, наконец, запускает приложение в Kubernetes.
Оболочка
xxxxxxxxxx
1
$ skaffold dev
С тех пор, как мы объявили три экземпляра нашего приложения deployment.yaml, запущены три модуля. Если обнаружение Hazelcast прошло успешно, вы должны увидеть следующий фрагмент журналов pod, распечатанных Skaffold.
Давайте посмотрим на беговые капсулы.
И список услуг. HTTP API доступен за пределами Minikube под портом 32090.
Теперь мы можем отправить несколько тестовых запросов. Мы начнем с вызова POST /employeesметода для добавления некоторых Employeeобъектов в кластер Hazelcast. Затем мы выполним некоторые методы поиска с использованием GET /employees/{id}. Поскольку все методы успешно завершены, мы должны взглянуть на журналы, которые четко показывают работу кластера Hazelcast.
Оболочка
xxxxxxxxxx
1
$ curl -X POST http://192.168.99.100:32090/employees -d '{"id":1,"personId":1,"company":"Test1","position":"Developer","salary":2000}' -H "Content-Type: application/json"
2
{"id":1,"personId":1,"company":"Test1","position":"Developer","salary":2000}
3
$ curl -X POST http://192.168.99.100:32090/employees -d '{"id":2,"personId":2,"company":"Test2","position":"Developer","salary":5000}' -H "Content-Type: application/json"
4
{"id":2,"personId":2,"company":"Test2","position":"Developer","salary":5000}
5
$ curl -X POST http://192.168.99.100:32090/employees -d '{"id":3,"personId":3,"company":"Test2","position":"Developer","salary":5000}' -H "Content-Type: application/json"
6
{"id":3,"personId":3,"company":"Test2","position":"Developer","salary":5000}
7
$ curl -X POST http://192.168.99.100:32090/employees -d '{"id":4,"personId":4,"company":"Test3","position":"Developer","salary":9000}' -H "Content-Type: application/json"
8
{"id":4,"personId":4,"company":"Test3","position":"Developer","salary":9000}
9
$ curl http://192.168.99.100:32090/employees/1
10
{"id":1,"personId":1,"company":"Test1","position":"Developer","salary":2000}
11
$ curl http://192.168.99.100:32090/employees/2
12
{"id":2,"personId":2,"company":"Test2","position":"Developer","salary":5000}
13
$ curl http://192.168.99.100:32090/employees/3
14
{"id":3,"personId":3,"company":"Test2","position":"Developer","salary":5000}
Вот экран с журналами из коробочек, распечатанных Скаффолдом. Skaffold печатает идентификатор модуля для каждой отдельной строки журнала. Давайте внимательнее посмотрим на бревна. Запрос на добавление Employee с id=1 обрабатывается приложением, запущенным на модуле 5b758cc977-s6ptd. Когда мы вызываем метод find с использованием id=1, он обрабатывается приложением на модуле 5b758cc977-2fj2h. Это доказывает, что кластер Hazelcast работает правильно. Такое же поведение может наблюдаться для других тестовых запросов.
Мы также можем вызвать некоторые другие методы поиска.
Оболочка
x
1
$ curl http://192.168.99.100:32090/employees/company/Test2/position/Developer
2
[{"id":2,"personId":2,"company":"Test2","position":"Developer","salary":5000},{"id":3,"personId":3,"company":"Test2","position":"Developer","salary":5000}]
3
$ curl http://192.168.99.100:32090/employees/salary/3000
4
[{"id":2,"personId":2,"company":"Test2","position":"Developer","salary":5000},{"id":4,"personId":4,"company":"Test3","position":"Developer","salary":9000},{"id":3,"personId":3,"company":"Test2","position":"Developer","salary":5000}]
Давайте проверим другой сценарий. Мы удалим один модуль из кластера, как показано ниже.
Затем мы отправим несколько тестовых запросов GET /employees/{id}. Независимо от того, какой экземпляр приложения обрабатывается, запрос объекта возвращается.
Дальнейшее чтение
Запуск приложения Spring Boot на Kubernetes Minikube в Windows (часть 1)
Refcard: собственные облачные сетки данных: Hazelcast IMDG с Kubernetes