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
apiVersion v1
2
kind Service
3
metadata
4
name hazelcast-service
5
spec
6
selector
7
app employee-service
8
ports
9
name hazelcast
10
port5701
11
type LoadBalancer
Вот определение развертывания и обслуживания Kubernetes для нашего примера приложения. Мы устанавливаем три реплики для нашего развертывания. Мы также выставляем два порта вне контейнеров.
YAML
xxxxxxxxxx
1
apiVersion apps/v1
2
kind Deployment
3
metadata
4
name employee-service
5
labels
6
app employee-service
7
spec
8
replicas3
9
selector
10
matchLabels
11
app employee-service
12
template
13
metadata
14
labels
15
app employee-service
16
spec
17
containers
18
name employee-service
19
image piomin/employee-service
20
ports
21
name http
22
containerPort8080
23
name multicast
24
containerPort5701
25
---
26
apiVersion v1
27
kind Service
28
metadata
29
name employee-service
30
labels
31
app employee-service
32
spec
33
ports
34
port8080
35
protocol TCP
36
selector
37
app employee-service
38
type NodePort
Фактически это все, что нужно сделать для успешного запуска кластера 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