Roman Kryvolapov Engineering Blog

Java Spring — Мікросервіси

Як можуть взаємодіяти мікросервіси

У мікросервісній архітектурі мікросервіси можуть взаємодіяти один з одним кількома способами, такими як синхронні та асинхронні виклики. Розгляньмо основні підходи до взаємодії мікросервісів:

Синхронні виклики (HTTP/REST):
Це найпоширеніший спосіб взаємодії мікросервісів. Один мікросервіс робить HTTP-запит до іншого мікросервісу. У Spring Boot це можна реалізувати за допомогою RestTemplate або WebClient.

Приклад використання RestTemplate:

import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
@Service
class UserService {
private val restTemplate = RestTemplate()
fun getUserDetails(userId: Long): User {
val url = "http://order-service/orders/user/$userId"
val response = restTemplate.getForObject(url, User::class.java)
return response ?: throw RuntimeException("User not found")
}
}

Приклад використання WebClient:

import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
@Service
class UserService {
private val webClient = WebClient.create("http://order-service")
fun getUserDetails(userId: Long): User {
return webClient.get()
.uri("/orders/user/$userId")
.retrieve()
.bodyToMono(User::class.java)
.block() ?: throw RuntimeException("User not found")
}
}

Асинхронні виклики (повідомлення):
Мікросервіси можуть взаємодіяти асинхронно, обмінюючись повідомленнями через брокери повідомлень, такі як RabbitMQ, Apache Kafka та інші.

Приклад використання RabbitMQ:

import org.springframework.amqp.core.Queue
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class RabbitMQConfig {
@Bean
fun queue(): Queue {
return Queue("example.queue", false)
}
}
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.stereotype.Component
@Component
class MessageSender(private val rabbitTemplate: RabbitTemplate) {
fun sendMessage(message: String) {
rabbitTemplate.convertAndSend("example.queue", message)
}
}
import org.springframework.amqp.rabbit.annotation.RabbitListener
import org.springframework.stereotype.Component
@Component
class MessageReceiver {
@RabbitListener(queues = ["example.queue"])
fun receiveMessage(message: String) {
println("Received message: $message")
}
}

Взаємодія через базу даних:
Мікросервіси можуть взаємодіяти через спільну базу даних, однак це не рекомендується, оскільки порушує принцип ізоляції даних у мікросервісній архітектурі. Краще використовувати базу даних лише для зберігання даних, а взаємодію здійснювати через API.

Використання сервісів виявлення та маршрутизації:
Eureka (виявлення сервісів)

Приклад конфігурації Eureka:

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.client.discovery.DiscoveryClient
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
@Service
class UserService {
@Autowired
private lateinit var discoveryClient: DiscoveryClient
private val restTemplate = RestTemplate()
fun getUserDetails(userId: Long): User {
val instances = discoveryClient.getInstances("order-service")
val orderServiceUri = instances[0].uri
val url = "$orderServiceUri/orders/user/$userId"
val response = restTemplate.getForObject(url, User::class.java)
return response ?: throw RuntimeException("User not found")
}
}

Spring Cloud Gateway (маршрутизація):
використовується для маршрутизації запитів до відповідних мікросервісів.
Приклад конфігурації Gateway:

Використання gRPC:
gRPC — це сучасний RPC (Remote Procedure Call) фреймворк, який використовує Protocol Buffers і підтримує асинхронні виклики.

Приклад використання gRPC (Протокол (user.proto)):

syntax = "proto3";
option java_package = "com.example.demo";
option java_multiple_files = true;
service UserService {
rpc GetUserDetails (UserRequest) returns (UserResponse);
}
message UserRequest {
int64 userId = 1;
}
message UserResponse {
int64 id = 1;
string username = 2;
string password = 3;
string role = 4;
}
import io.grpc.stub.StreamObserver
import net.devh.boot.grpc.server.service.GrpcService
@GrpcService
class UserServiceImpl : UserServiceGrpc.UserServiceImplBase() {
override fun getUserDetails(request: UserRequest, responseObserver: StreamObserver<UserResponse>) {
val user = UserResponse.newBuilder()
.setId(request.userId)
.setUsername("testuser")
.setPassword("password")
.setRole("ROLE_USER")
.build()
responseObserver.onNext(user)
responseObserver.onCompleted()
}
}
import net.devh.boot.grpc.client.inject.GrpcClient
import org.springframework.stereotype.Service
@Service
class UserServiceClient {
@GrpcClient("user-service")
private lateinit var userServiceStub: UserServiceGrpc.UserServiceBlockingStub
fun getUserDetails(userId: Long): UserResponse {
val request = UserRequest.newBuilder().setUserId(userId).build()
return userServiceStub.getUserDetails(request)
}
}

Що таке Dockerfile і Docker Compose

Dockerfile:
використовується для створення індивідуальних Docker-образів. Це основний спосіб опису того, як побудувати образ контейнера з потрібними залежностями та конфігураціями.

# Використання базового образу
FROM openjdk:11-jre-slim
# Встановлення робочої директорії
WORKDIR /app
# Копіювання jar-файла в контейнер
COPY target/myapp.jar /app/myapp.jar
# Зазначення команди для запуску застосунку
CMD ["java", "-jar", "myapp.jar"]

Docker Compose:
використовується для керування багатоконтейнерними застосунками. Це інструмент для оркестрації, який дозволяє запускати та керувати кількома контейнерами як одним цілим, визначаючи їхню взаємодію та залежності.

version: '3.8'
services:
web:
build: .
ports:
- "8080:8080"
depends_on:
- db
db:
image: postgres:13
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydatabase
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:

Обидва інструменти часто використовуються разом:
спочатку створюється Dockerfile для кожного сервісу, потім Docker Compose файл для керування всіма цими сервісами в одному застосунку.

Як використовувати Docker у Spring

Docker — це платформа для розробки, доставки та запуску застосунків у контейнерах. Контейнери дозволяють упакувати застосунок з його залежностями та забезпечити його ізоляцію і портативність.

Основні концепції Docker:

Контейнери:
Ізольовані середовища, у яких запускаються застосунки.

Образи:
Шаблони, що використовуються для створення контейнерів.

Dockerfile:
Скрипт, що описує, як створювати Docker-образ.

Docker Hub:
Хмарний реєстр, де можна зберігати Docker-образи та ділитися ними.

Основні команди Docker:

docker build:
Створення Docker-образу з Dockerfile.

docker run:
Запуск контейнера з Docker-образу.

docker pull:
Завантаження Docker-образу з реєстру.

docker push:
Завантаження Docker-образу до реєстру.

docker ps:
Список запущених контейнерів.

docker stop/start:
Зупинка/запуск контейнера.

# Етап 1: Збірка застосунку
FROM openjdk:21-jdk-slim AS build
WORKDIR /app
# Копіюємо файли збірки Gradle і вихідний код
COPY build.gradle settings.gradle gradlew gradlew.bat ./
COPY gradle gradle
COPY src src
# Встановлюємо дозволи на виконання файлу gradlew
RUN chmod +x ./gradlew
# Завантажуємо залежності і збираємо проєкт
RUN ./gradlew bootJar
# Етап 2: Створення мінімального образу для запуску застосунку
FROM openjdk:21-jdk-slim
WORKDIR /app
# Копіюємо зібраний jar-файл з етапу збірки
COPY --from=build /app/build/libs/*.jar /app/app.jar
# Вказуємо команду для запуску Spring Boot застосунку
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Збірка проєкту:
Зберіть проєкт за допомогою Maven або Gradle, щоб отримати виконуваний jar-файл у теці target.

./gradlew build

Збірка Docker-образу:
Використайте команду docker build, щоб створити Docker-образ з Dockerfile.

docker build -t your-dockerhub-username/demo .

Запуск контейнера:
Запустіть контейнер за допомогою команди docker run.

docker run -p 8080:8080 your-dockerhub-username/demo

Тепер ваш застосунок доступний за адресою http://localhost:8080.

Завантаження образу в Docker Hub:
Увійдіть у Docker Hub і завантажте ваш образ.

docker login
docker push your-dockerhub-username/demo

Корисні команди Docker:

docker images:
Список усіх образів.

docker ps -a:
Список усіх контейнерів.

docker stop:
Зупинка контейнера.

docker start:
Запуск зупиненого контейнера.

docker rm:
Видалення контейнера.

docker rmi :
Видалення образу.

Як використовувати Docker Compose у Spring

Docker Compose — це інструмент для визначення та керування багатоконтейнерними Docker-застосунками. За допомогою Docker Compose ви можете описати послуги, мережі та томи в одному файлі YAML, а потім легко розгорнути їх за допомогою однієї команди.

Приклад використання Docker Compose:
Припустімо, у нас є Spring Boot застосунок, який використовує базу даних PostgreSQL. Ми хочемо розгорнути ці два сервіси разом за допомогою Docker Compose.

spring.datasource.url=jdbc:postgresql://db:5432/mydatabase
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update

Створення Dockerfile:

# Use the official image as a parent image
FROM openjdk:11-jre-slim
# Set the working directory in the container
WORKDIR /app
# Copy the jar file to the container
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
# Run the jar file
ENTRYPOINT ["java", "-jar", "app.jar"]

Створіть файл docker-compose.yml у кореневій директорії проєкту:

version: '3.8' # Вказує версію формату файлу Docker Compose
services: # Визначає список сервісів, які будуть розгорнуті
app: # Визначення сервісу для Spring Boot застосунку
image: your-dockerhub-username/demo # Ім'я Docker-образу
build: # Визначення процесу збірки Docker-образу
context: . # Контекст збірки, поточна директорія
dockerfile: Dockerfile # Ім'я Dockerfile, який буде використовуватися для збірки
ports:
- "8080:8080" # Прокидання порту 8080 хоста на порт 8080 контейнера
environment: # Змінні середовища для налаштування застосунку
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/mydatabase # URL підключення до бази даних
SPRING_DATASOURCE_USERNAME: postgres # Ім'я користувача для бази даних
SPRING_DATASOURCE_PASSWORD: postgres # Пароль для бази даних
depends_on: # Визначає залежності від інших сервісів
- db # Залежить від сервісу db
db: # Визначення сервісу для бази даних PostgreSQL
image: postgres:13 # Ім'я Docker-образу PostgreSQL версії 13
environment: # Змінні середовища для налаштування PostgreSQL
POSTGRES_DB: mydatabase # Ім'я бази даних, яка буде створена
POSTGRES_USER: postgres # Ім'я користувача бази даних
POSTGRES_PASSWORD: postgres # Пароль для користувача бази даних
ports:
- "5432:5432" # Прокидання порту 5432 хоста на порт 5432 контейнера
volumes:
- postgres_data:/var/lib/postgresql/data # Створення тому для зберігання даних PostgreSQL
volumes: # Визначає список томів, які будуть використовуватися сервісами
postgres_data: # Том для зберігання даних PostgreSQL, щоб вони зберігалися між перезапусками контейнера

Пояснення до кожної секції:

version:
Вказує версію формату файлу Docker Compose. У цьому випадку використовується версія 3.8.

services:
Визначає всі сервіси, які будуть розгорнуті.

app:
Сервіс для Spring Boot застосунку.

image:
Ім'я Docker-образу, який буде використовуватися для запуску контейнера.

build:
Визначає параметри збірки Docker-образу.

context:
Директорія, у якій знаходиться Dockerfile.

dockerfile:
Ім'я файлу Dockerfile.

ports:
Прокидання порту 8080 хоста на порт 8080 контейнера.

environment:
Змінні середовища для налаштування підключення до бази даних.

depends_on:
Визначає, що цей сервіс залежить від сервісу db.

db:
Сервіс для бази даних PostgreSQL.

image:
Ім'я Docker-образу PostgreSQL версії 13.

environment:
Змінні середовища для налаштування бази даних.

ports:
Прокидання порту 5432 хоста на порт 5432 контейнера.

volumes:
Створення тому для зберігання даних PostgreSQL.

volumes:
Визначає том, який буде використовуватися для зберігання даних PostgreSQL.

Як це працює:

Збірка та запуск:
Під час виконання команди docker-compose up — build, Docker Compose спочатку збирає Docker-образ для сервісу app з використанням зазначеного Dockerfile. Потім запускаються обидва сервіси (app і db), причому сервіс app залежить від сервісу db, і тому db запускається першим.

Змінні середовища:
Змінні середовища використовуються для налаштування параметрів підключення до бази даних у Spring Boot застосунку.

Прокидання портів:
Порти прокидаються з контейнерів на хост-машину, щоб можна було отримати доступ до застосунку та бази даних.

Том для даних:
Створюється том postgres_data для зберігання даних бази даних PostgreSQL, щоб дані зберігалися між перезапусками контейнера.

Цей файл docker-compose.yml дозволяє легко розгорнути багатоконтейнерний застосунок з використанням Docker Compose, забезпечуючи узгодженість і зручність керування контейнерами.

Збірка та запуск:
Запустіть docker-compose для збірки та запуску всіх сервісів.

docker-compose up --build

Ця команда:
Збере Docker-образ для вашого Spring Boot застосунку.
Запустить контейнери для застосунку та бази даних PostgreSQL.
Налаштує зв'язок між контейнерами.

Як використовувати Kubernetes у Spring

Kubernetes — це система оркестрації контейнерів з відкритим кодом, яка дозволяє автоматизувати розгортання, масштабування та керування контейнеризованими застосунками. Вона забезпечує платформу для запуску контейнерів, керування ними та масштабування.

Ось покрокова інструкція з використання Kubernetes для розгортання Spring Boot застосунку.

Попередні вимоги:

Docker:
Переконайтеся, що Docker встановлений і налаштований.

Kubernetes:
Встановіть і налаштуйте Kubernetes (наприклад, з використанням Minikube для локальної розробки).

kubectl:
Встановіть утиліту командного рядка kubectl для взаємодії з кластером Kubernetes.

Створення Docker-образу:

# Use the official image as a parent image
FROM openjdk:11-jre-slim
# Set the working directory in the container
WORKDIR /app
# Copy the jar file to the container
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
# Run the jar file
ENTRYPOINT ["java", "-jar", "app.jar"]

Зберіть Docker-образ і завантажте його в Docker Hub (або будь-який інший реєстр Docker-образів).

# Збірка Docker-образу
docker build -t your-dockerhub-username/demo .
# Вхід у Docker Hub
docker login
# Завантаження Docker-образу в Docker Hub
docker push your-dockerhub-username/demo

Створення Kubernetes маніфестів:
Створіть маніфести Kubernetes для розгортання, служби та конфігурації Ingress.

deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: demo
spec:
replicas: 3
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: demo
image: your-dockerhub-username/demo:latest
ports:
- containerPort: 8080

service.yaml:

apiVersion: v1
kind: Service
metadata:
name: demo-service
spec:
selector:
app: demo
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer

ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: demo.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-service
port:
number: 80

Застосування маніфестів Kubernetes:
Використайте команду kubectl для застосування маніфестів у вашому кластері Kubernetes.

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

Налаштування DNS:
Для локальної розробки з Minikube додайте наступний запис у файл /etc/hosts:

<MINIKUBE_IP> demo.local

Отримайте IP Minikube за допомогою команди:

minikube ip

Перевірка розгортання:
Перевірте стан вашого розгортання, служби та ingress за допомогою команд:

kubectl get deployments
kubectl get services
kubectl get ingress

Відкрийте браузер і перейдіть за адресою http://demo.local, щоб побачити ваш розгорнутий Spring Boot застосунок.

Як використовувати Apache Kafka у Spring

Apache Kafka — це розподілена стрімінгова платформа, яка використовується для побудови систем обробки даних у реальному часі. Kafka спочатку була розроблена в LinkedIn і відкрита як проєкт з відкритим кодом у 2011 році. Вона використовується для публікації, зберігання та обробки потоків записів у реальному часі.

Основні компоненти Kafka:

Producer:
Надсилає записи (повідомлення) в топіки Kafka.

Consumer:
Читає записи (повідомлення) з топіків Kafka.

Broker:
Kafka-сервер, який приймає дані від продюсерів, зберігає їх і передає їх консюмерам. Кластер Kafka складається з одного або кількох брокерів.

Topic:
Логічна категорія або канал, куди продюсери надсилають дані і звідки консюмери їх читають. Топік розбивається на партиції для забезпечення паралелізму та масштабованості.

Partition:
Підрозділ топіка. Кожна партиція є впорядкованим і незмінним логом, куди продюсери додають повідомлення.

Zookeeper:
Система координації, що використовується Kafka для керування метаданими кластера, відстеження статусу брокерів і топіків.

Як працює Kafka:

Публікація повідомлень:
Продуктори публікують повідомлення у визначені топіки. Повідомлення зберігаються в партиціях топіка у вигляді логів.

Зберігання повідомлень:
Повідомлення зберігаються в партиціях топіків на диску і можуть бути налаштовані для зберігання на певний час або до досягнення певного обсягу даних.

Читання повідомлень:
Консьюмери підписуються на топіки і читають повідомлення з партицій. Kafka дозволяє консьюмерам контролювати, з якого місця вони починають читати повідомлення, що забезпечує високу гнучкість і можливість повторного читання даних.

Масштабованість:
Партиції дозволяють розподіляти навантаження між кількома брокерами, забезпечуючи горизонтальне масштабування.

Фолтолерантність:
Реплікація партицій між кількома брокерами забезпечує високу доступність і стійкість до збоїв.

Налаштування Kafka:
Встановіть і налаштуйте Apache Kafka та Zookeeper, використовуючи Docker Compose.
Створіть файл docker-compose.yml

version: '3.8'
services:
zookeeper:
image: bitnami/zookeeper:latest
container_name: zookeeper
ports:
- "2181:2181"
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
kafka:
image: bitnami/kafka:latest
container_name: kafka
ports:
- "9092:9092"
environment:
- KAFKA_BROKER_ID=1
- KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
- ALLOW_PLAINTEXT_LISTENER=yes

перейдіть у командному рядку в директорію. файла і запустіть:

docker-compose up -d

Перевірте стан

docker-compose ps

Створення виробника подій:
Створіть Spring Boot застосунок із залежностями Kafka.

dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.kafka:spring-kafka")
}
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=my-group
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer

або через клас конфігурації

import org.apache.kafka.clients.producer.ProducerConfig
import org.apache.kafka.common.serialization.StringSerializer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.kafka.core.DefaultKafkaProducerFactory
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.kafka.core.ProducerFactory
import org.springframework.kafka.support.serializer.JsonSerializer
@Configuration
class KafkaProducerConfig {
@Bean
fun producerFactory(): ProducerFactory<String, String> {
val configProps = HashMap<String, Any>()
configProps[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = "localhost:9092"
configProps[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
configProps[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java
return DefaultKafkaProducerFactory(configProps)
}
@Bean
fun kafkaTemplate(): KafkaTemplate<String, String> {
return KafkaTemplate(producerFactory())
}
}
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.common.serialization.StringDeserializer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.kafka.annotation.EnableKafka
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory
import org.springframework.kafka.core.ConsumerFactory
import org.springframework.kafka.core.DefaultKafkaConsumerFactory
import org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
import org.springframework.kafka.support.serializer.JsonDeserializer
@EnableKafka
@Configuration
class KafkaConsumerConfig {
@Bean
fun consumerFactory(): ConsumerFactory<String, String> {
val configProps = HashMap<String, Any>()
configProps[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = "localhost:9092"
configProps[ConsumerConfig.GROUP_ID_CONFIG] = "group_id"
configProps[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = ErrorHandlingDeserializer::class.java
configProps[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = ErrorHandlingDeserializer::class.java
configProps[ErrorHandlingDeserializer.VALUE_DESERIALIZER_CLASS] = JsonDeserializer::class.java.name
return DefaultKafkaConsumerFactory(configProps, StringDeserializer(), JsonDeserializer())
}
@Bean
fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory<String, String> {
val factory = ConcurrentKafkaListenerContainerFactory<String, String>()
factory.consumerFactory = consumerFactory()
return factory
}
}

Producer і Consumer:

import org.springframework.kafka.core.KafkaTemplate
import org.springframework.stereotype.Service
@Service
class EventProducer(private val kafkaTemplate: KafkaTemplate<String, String>) {
fun sendMessage(topic: String, message: String) {
kafkaTemplate.send(topic, message)
}
}
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.stereotype.Service
@Service
class EventConsumer {
@KafkaListener(topics = ["topic_name"], groupId = "group_id")
fun consume(message: String) {
println("Consumed message: $message")
}
}

Як використовувати RabbitMQ у Spring

RabbitMQ — це програмне забезпечення для черг повідомлень (message broker), яке дозволяє застосункам обмінюватися повідомленнями та виконувати завдання асинхронно. Воно підтримує кілька протоколів обміну повідомленнями і широко використовується для створення розподілених і масштабованих систем.

Основні концепції RabbitMQ:

Протокол AMQP:
RabbitMQ підтримує протокол AMQP (Advanced Message Queuing Protocol), який визначає правила обміну повідомленнями між клієнтами та брокерами.

Черги (Queues):
Черга — це буфер для зберігання повідомлень. Повідомлення надсилаються в чергу, де вони чекають на обробку одержувачем.

Обмінники (Exchanges):
Обмінник отримує повідомлення від виробника і маршрутизує їх в одну або кілька черг залежно від встановлених правил.

Прив'язки (Bindings):
Прив'язка пов'язує чергу з обмінником і визначає правила маршрутизації повідомлень з обмінника в чергу.

Повідомлення (Messages):
Повідомлення — це дані, які передаються між застосунками через черги. Кожне повідомлення складається із заголовка й тіла (корисного навантаження).

Виробники (Producers):
Виробники надсилають повідомлення в обмінники.

Споживачі (Consumers):
Споживачі отримують повідомлення з черг і обробляють їх.

Приклад роботи RabbitMQ:
Виробник (Producer) надсилає повідомлення в обмінник (Exchange).
Обмінник маршрутизує повідомлення у відповідну чергу (Queue) на основі встановлених правил.
Споживач (Consumer) отримує повідомлення з черги і обробляє його.

Щоб використовувати RabbitMQ у Kotlin з використанням Spring Boot, ви можете скористатися бібліотекою Spring AMQP (Spring for RabbitMQ)

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.jetbrains.kotlin:kotlin-reflect'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}
import org.springframework.amqp.core.Queue
import org.springframework.amqp.rabbit.connection.ConnectionFactory
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class RabbitConfig {
@Bean
fun queue(): Queue {
return Queue("myQueue", false)
}
@Bean
fun rabbitTemplate(connectionFactory: ConnectionFactory): RabbitTemplate {
return RabbitTemplate(connectionFactory)
}
}
import org.springframework.amqp.rabbit.core.RabbitTemplate
import org.springframework.stereotype.Service
@Service
class MessageSender(private val rabbitTemplate: RabbitTemplate) {
fun sendMessage(message: String) {
rabbitTemplate.convertAndSend("myQueue", message)
}
}
import org.springframework.amqp.rabbit.annotation.RabbitListener
import org.springframework.stereotype.Service
@Service
class MessageListener {
@RabbitListener(queues = ["myQueue"])
fun receiveMessage(message: String) {
println("Received message: $message")
}
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
@RestController
class MessageController(private val messageSender: MessageSender) {
// http://localhost:8080/send?message=Hello
@GetMapping("/send")
fun send(@RequestParam message: String) {
messageSender.sendMessage(message)
return "Message sent: $message"
}
}

Чим Kafka відрізняється від RabbitMQ

Kafka і RabbitMQ — це дві популярні системи обміну повідомленнями, які використовуються для передачі повідомлень між компонентами розподілених систем. Однак вони мають різні архітектурні підходи, цільові сценарії використання та особливості.

Apache Kafka:

Архітектура:

Журнал публікації-підписки (log-based pub/sub):
Kafka зберігає повідомлення у вигляді логів, що дозволяє підписникам читати повідомлення з певного зміщення.

Брокер повідомлень:
Kafka складається з кластерів брокерів, які зберігають дані розподілено.

Зберігання повідомлень:
Повідомлення зберігаються на диску, і кожне повідомлення має унікальне зміщення. Це дозволяє повторно читати повідомлення і перемотувати журнал.
Підтримка довготривалого зберігання даних.

Продуктивність:
Висока продуктивність і пропускна здатність, що підходить для обробки великих обсягів даних у реальному часі.

Підтримка масштабування:
Легко масштабується горизонтально за рахунок додавання нових брокерів і розділів (partitions).

Цільові сценарії використання:
Потокова обробка даних (stream processing).
Реалізація подієвих систем.
Аналітика даних у реальному часі.

Екосистема:
Включає такі компоненти, як Kafka Streams для обробки потоків і Kafka Connect для інтеграції з різними джерелами даних.

RabbitMQ:

Архітектура:

Черги повідомлень:
RabbitMQ використовує концепцію черг повідомлень і маршрутизації (routing).

Брокер повідомлень:
Повідомлення передаються через обмінники (exchanges) і черги (queues).

Зберігання повідомлень:
Повідомлення можуть зберігатися в оперативній пам'яті або на диску.
Підтримує надійну доставку повідомлень через підтвердження (acknowledgments) і повторну спробу (retry).

Продуктивність:
Хороша продуктивність для широкого спектра сценаріїв, але може бути менш ефективною при обробці великих обсягів даних порівняно з Kafka.

Підтримка масштабування:
Підтримка кластеризації та федерації для горизонтального масштабування, але може бути складнішою в налаштуванні та керуванні порівняно з Kafka.

Цільові сценарії використання:
Класична черга завдань (task queue) для асинхронної обробки.
Реалізація системи обміну повідомленнями зі складною маршрутизацією.
Інтеграція та обмін даними між різнорідними системами.

Екосистема:
Підтримка різних протоколів (AMQP, MQTT, STOMP).
Велика кількість плагінів для розширення функціональності.

Kafka і RabbitMQ вирішують різні завдання у сфері передачі повідомлень і мають свої переваги та обмеження. Kafka підходить для обробки великих обсягів даних і потокової обробки, тоді як RabbitMQ краще справляється із завданнями асинхронної обробки та складною маршрутизацією повідомлень. Вибір між ними залежить від конкретних вимог і сценаріїв використання у вашому проєкті.

Що таке Eureka Service

Eureka Service — це частина набору інструментів для розробки мікросервісів від Netflix, який називається Netflix OSS. Він використовується для реєстрації та виявлення сервісів у розподілених системах. Eureka Service є центральною частиною сервісу виявлення (service discovery), який дозволяє різним мікросервісам знаходити один одного та взаємодіяти між собою.

Основні компоненти Eureka:

Eureka Server:
Центральний сервер, який діє як реєстратор для всіх мікросервісів. Мікросервіси реєструються в Eureka Server і повідомляють про свій стан (healthy, down, etc.).

Eureka Client:
Клієнти Eureka, які реєструють себе на Eureka Server і можуть використовувати його для виявлення інших сервісів.

eureka-service-project
├── eureka-server
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ ├── java (або kotlin)
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── eurekaserver
│ │ │ │ └── EurekaServerApplication.kt
│ │ │ ├── resources
│ │ │ │ └── application.properties
│ │ └── test
│ │ ├── java (або kotlin)
│ │ │ └── com
│ │ │ └── example
│ │ │ └── eurekaserver
│ │ │ └── EurekaServerApplicationTests.kt
├── eureka-client
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ ├── java (або kotlin)
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── eurekaclient
│ │ │ │ ├── EurekaClientApplication.kt
│ │ │ │ └── ServiceInstanceRestController.kt
│ │ │ ├── resources
│ │ │ │ └── application.properties
│ │ └── test
│ │ ├── java (або kotlin)
│ │ │ └── com
│ │ │ └── example
│ │ │ └── eurekaclient
│ │ │ └── EurekaClientApplicationTests.kt
└── settings.gradle

Як працює Eureka Service:

Реєстрація сервісів:
Кожен мікросервіс, який є клієнтом Eureka, реєструє себе на Eureka Server під час запуску, надаючи свою інформацію, таку як адреса, порт та ідентифікатор сервісу.

Оновлення статусу:
Клієнти регулярно надсилають “пінги” (heartbeats) на Eureka Server для підтвердження, що вони все ще активні.

Виявлення сервісів:
Мікросервіси можуть використовувати Eureka Client для отримання списку всіх доступних сервісів, зареєстрованих в Eureka Server, і для взаємодії з ними.

Основні анотації та їх використання в Eureka:

@EnableEurekaServer:
Вмикає Eureka Server.

@EnableEurekaClient:
Вмикає Eureka Client для реєстрації та виявлення сервісів.

@EnableDiscoveryClient:
Вмикає загальний механізм виявлення сервісів (може використовуватися з різними системами виявлення, включно з Eureka).

@LoadBalanced:
Маркує RestTemplate або WebClient для використання клієнтського балансування навантаження.

Приклад використання Eureka Service:
У settings.gradle потрібно додати includeBuild усі модулі

Приклад для Eureka Server:

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.server.enable-self-preservation=false

server.port=8761:
Задає порт, на якому буде запущений Eureka Server. За замовчуванням Eureka Server запускається на порту 8080.

eureka.client.register-with-eureka=false:
Вказує, що Eureka Server не повинен реєструватися сам у собі як клієнт. Це параметр конфігурації клієнта Eureka, який вказує, що цей сервер Eureka не буде реєструватися в іншому сервері Eureka.

eureka.client.fetch-registry=false:
Вказує, що Eureka Server не повинен видобувати (fetch) реєстр інших сервісів. Це також параметр конфігурації клієнта Eureka, який вимикає спроби отримання списку всіх зареєстрованих сервісів.

eureka.server.enable-self-preservation=false:
Вимикає режим самозбереження (self-preservation mode) на Eureka Server. Цей режим призначений для захисту від втрати реєстрацій сервісів, коли сервер не отримує пінгів від клієнтів протягом тривалого часу. Вимкнення цього режиму дозволяє негайно видаляти сервіси з реєстру, якщо вони не надсилають пінги, що може бути корисно для тестування, але не рекомендується для продакшн середовища.

@EnableEurekaServer:
Ця анотація використовується для увімкнення сервера Eureka. Вона вказується на основному класі застосунку, щоб позначити його як Eureka Server.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
class EurekaServerApplication
fun main(args: Array<String>) {
runApplication<EurekaServerApplication>(*args)
}

Приклад Eureka Client:

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
server.port=0
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

server.port=0:
Задає порт, на якому буде запущений Eureka Client. У цьому випадку клієнтський застосунок працюватиме на порту, заданому автоматично.

**eureka.client.service-url.defaultZone=http://localhost:8761/eureka/:**\ Вказує URL для реєстрації клієнта на Eureka Server. Параметр defaultZone визначає адресу сервера Eureka, з яким буде взаємодіяти клієнт для реєстрації та виявлення сервісів. У цьому випадку клієнт буде реєструватися на Eureka Server, що працює на http://localhost:8761/eureka/.

Додаткові параметри, які можуть використовуватися в application.properties:

Для Eureka Server:

eureka.instance.hostname:
Встановлює ім'я хоста для Eureka Server.

eureka.instance.prefer-ip-address:
Вказує, чи слід використовувати IP-адресу замість імені хоста для реєстрації.

Для Eureka Client:

eureka.instance.hostname:
Встановлює ім'я хоста для Eureka Client.

eureka.instance.prefer-ip-address:
Вказує, чи слід використовувати IP-адресу замість імені хоста для реєстрації клієнта.

eureka.client.initial-instance-info-replication-interval-seconds:
Задає інтервал у секундах між повтореннями надсилання метаданих екземпляра в Eureka Server.

eureka.client.registry-fetch-interval-seconds:
Задає інтервал у секундах між спробами клієнта отримати оновлений реєстр сервісів від Eureka Server.

eureka.client.instance-info-replication-interval-seconds:
Задає інтервал у секундах між надсиланнями інформації про екземпляр сервісу в Eureka Server.

@EnableEurekaClient:
Ця анотація використовується для увімкнення клієнта Eureka. Вона вказується на основному класі застосунку, щоб позначити його як клієнт Eureka. Клієнт автоматично реєструється на Eureka Server і може використовувати його для виявлення інших сервісів.

@EnableDiscoveryClient:
Ця анотація є більш загальною анотацією для увімкнення виявлення сервісів. Вона дозволяє застосунку використовувати різні механізми виявлення сервісів, включно з Eureka. Вона використовується аналогічно до анотації @EnableEurekaClient.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cloud.netflix.eureka.EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
class EurekaClientApplication
fun main(args: Array<String>) {
runApplication<EurekaClientApplication>(*args)
}
import org.springframework.cloud.client.discovery.DiscoveryClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class ServiceInstanceRestController(private val discoveryClient: DiscoveryClient) {
@GetMapping("/service-instances")
fun serviceInstances(): List<String> {
return discoveryClient.services
}
}

@LoadBalanced:
Ця анотація використовується для маркування RestTemplate або WebClient, щоб він використовував клієнтське балансування навантаження, засноване на Ribbon (якщо використовується). Це дозволяє RestTemplate або WebClient робити HTTP-запити до іменованих сервісів, зареєстрованих в Eureka, з балансуванням навантаження між екземплярами сервісу.

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cloud.client.loadbalancer.LoadBalanced
import org.springframework.context.annotation.Bean
import org.springframework.web.client.RestTemplate
@SpringBootApplication
class LoadBalancedApplication {
@Bean
@LoadBalanced
fun restTemplate(): RestTemplate {
return RestTemplate()
}
}
fun main(args: Array<String>) {
runApplication<LoadBalancedApplication>(*args)
}

Як реалізувати Spring Cloud Gateway

Spring Cloud Gateway — це сучасний шлюз API, побудований на основі Spring Framework 5, Spring Boot 2 і проєкту Spring WebFlux. Він надає потужні можливості для маршрутизації та керування API-трафіком у мікросервісній архітектурі. Spring Cloud Gateway спроєктований для забезпечення легкої інтеграції з іншими компонентами Spring Cloud і для роботи в асинхронному та реактивному середовищі.

Основні можливості Spring Cloud Gateway:

Маршрутизація запитів:
Spring Cloud Gateway може маршрутизувати запити до різних мікросервісів на основі різних критеріїв, таких як шлях, заголовки, параметри запиту та багато іншого.

Фільтри:
Дозволяє застосовувати фільтри до запитів і відповідей. Фільтри можуть змінювати запити, додавати або змінювати заголовки, виконувати автентифікацію та авторизацію, логування та багато іншого.

Балансування навантаження:
Вбудована підтримка балансування навантаження дозволяє розподіляти запити між кількома екземплярами мікросервісів.

Інтеграція з Eureka:
Spring Cloud Gateway легко інтегрується з Eureka та іншими системами виявлення сервісів для динамічної маршрутизації запитів до зареєстрованих сервісів.

Підтримка асинхронної обробки:
Побудований на реактивному стеку Spring WebFlux, що дозволяє обробляти велику кількість паралельних запитів з високою продуктивністю.

Безпека:
Підтримка різних механізмів автентифікації та авторизації, включно з OAuth2.

Приклад використання Spring Cloud Gateway:
Створіть новий проєкт Spring Boot із залежностями для Spring Cloud Gateway і Spring Boot Actuator.

eureka-service-project
├── eureka-server
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ └── ...
│ │ └── ...
├── eureka-client
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ └── ...
│ │ └── ...
├── api-gateway
│ ├── build.gradle
│ ├── src
│ │ ├── main
│ │ │ └── ...
│ │ └── ...
└── settings.gradle

Налаштування settings.gradle

rootProject.name = 'eureka-service-project'
include 'eureka-server', 'eureka-client', 'api-gateway'

api-gateway/build.gradle

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:2021.0.1"
}
}

api-gateway/src/main/resources/application.properties

server.port=8080
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

api-gateway/src/main/kotlin/com/example/apigateway/ApiGatewayApplication.kt

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.cloud.client.discovery.EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
class ApiGatewayApplication
fun main(args: Array<String>) {
runApplication<ApiGatewayApplication>(*args)
}

Пояснення до налаштувань:

server.port=8080:
API Gateway запускатиметься на порту 8080.

spring.cloud.gateway.discovery.locator.enabled=true:
Вмикає автоматичне виявлення маршрутів для мікросервісів, зареєстрованих в Eureka.

spring.cloud.gateway.discovery.locator.lower-case-service-id=true:
Приводить ідентифікатори сервісів до нижнього регістру для спрощення маршрутизації.

**eureka.client.service-url.defaultZone=http://localhost:8761/eureka/:**\ URL-адреса Eureka Server для реєстрації та виявлення сервісів.

Маршрутизація запитів:
Spring Cloud Gateway автоматично створює маршрути для всіх мікросервісів, зареєстрованих в Eureka. Наприклад, якщо у вас є мікросервіс з іменем eureka-client, запити до API Gateway на /eureka-client/** будуть перенаправлятися на відповідний мікросервіс.
Маршрутизація може бути налаштована в конфігураційному файлі або програмно.

src/main/resources/application.properties

server.port=8080
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
# Визначення маршрутів для eureka-client
spring.cloud.gateway.routes[0].id=eureka-client
spring.cloud.gateway.routes[0].uri=lb://eureka-client
spring.cloud.gateway.routes[0].predicates[0]=Path=/eureka-client/**
spring.cloud.gateway.routes[0].filters[0]=RewritePath=/eureka-client/(?<remaining>.*), /${remaining}

Пояснення до конфігурації:

server.port=8080:
Вказує, що Spring Cloud Gateway буде запущений на порту 8080.

spring.cloud.gateway.discovery.locator.enabled=true:
Вмикає автоматичне виявлення маршрутів для сервісів, зареєстрованих в Eureka.

spring.cloud.gateway.discovery.locator.lower-case-service-id=true:
Перетворює ідентифікатори сервісів у нижній регістр для спрощення маршрутизації.

**eureka.client.service-url.defaultZone=http://localhost:8761/eureka/:**\ Вказує URL Eureka Server для реєстрації та виявлення сервісів.

routes:
Налаштування маршрутів. У цьому випадку запити, що починаються з /eureka-client/**, будуть перенаправлені на сервіс eureka-client.

Як реалізувати перенаправлення за допомогою Spring Cloud Gateway

Припустімо, у нас є два мікросервіси, зареєстровані в Eureka: order-service та inventory-service.
Ми хочемо налаштувати маршрутизацію в Spring Cloud Gateway таким чином, щоб запити, що починаються з /orders/, перенаправлялися на order-service, а запити, що починаються з /inventory/, перенаправлялися на inventory-service.

server.port=8080
# Вмикаємо автоматичне виявлення маршрутів для сервісів, зареєстрованих в Eureka
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
# Вказуємо URL Eureka Server для реєстрації та виявлення сервісів
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
# Визначення маршрутів для order-service
spring.cloud.gateway.routes[0].id=order-service
spring.cloud.gateway.routes[0].uri=lb://order-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/orders/**
spring.cloud.gateway.routes[0].filters[0]=RewritePath=/orders/(?<remaining>.*), /${remaining}
# Визначення маршрутів для inventory-service
spring.cloud.gateway.routes[1].id=inventory-service
spring.cloud.gateway.routes[1].uri=lb://inventory-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/inventory/**
spring.cloud.gateway.routes[1].filters[0]=RewritePath=/inventory/(?<remaining>.*), /${remaining}

Пояснення параметрів:

server.port:
Вказує, що Spring Cloud Gateway буде запущений на порту 8080.

spring.cloud.gateway.discovery.locator.enabled:
Вмикає автоматичне виявлення маршрутів для сервісів, зареєстрованих в Eureka.

spring.cloud.gateway.discovery.locator.lower-case-service-id:
Перетворює ідентифікатори сервісів у нижній регістр для спрощення маршрутизації.

eureka.client.service-url.defaultZone:
Вказує URL Eureka Server для реєстрації та виявлення сервісів.

spring.cloud.gateway.routes:
routes: Секція для визначення маршрутів у Spring Cloud Gateway.

Маршрут для order-service:

id:
Унікальний ідентифікатор маршруту. У цьому випадку це order-service.

uri:
URI для перенаправлення запитів. Префікс lb:// вказує на використання вбудованого балансування навантаження (load balancer), а order-service — це ім'я сервісу, зареєстрованого в Eureka.

predicates:
Умови, які мають бути виконані для того, щоб запит був перенаправлений цим маршрутом.

Path=/orders/:
Умова шляху. Усі запити, що починаються з /orders/**, будуть відповідати цьому маршруту.

filters:
Перелік фільтрів, які будуть застосовуватися до запитів і відповідей.

RewritePath=/orders/(?.*), /${remaining}:
Фільтр, що переписує шлях запиту. У цьому випадку частина шляху /orders/ буде видалена із запиту перед його надсиланням в order-service. Решта шляху буде збережена і передана далі.

Маршрут для inventory-service:

id:
Унікальний ідентифікатор маршруту. У цьому випадку це inventory-service.

uri:
URI для перенаправлення запитів. Префікс lb:// вказує на використання вбудованого балансування навантаження (load balancer), а inventory-service — це ім'я сервісу, зареєстрованого в Eureka.

predicates:
Умови, які мають бути виконані для того, щоб запит був перенаправлений цим маршрутом.

Path=/inventory/:
Умова шляху. Усі запити, що починаються з /inventory/**, будуть відповідати цьому маршруту.

filters:
Перелік фільтрів, які будуть застосовуватися до запитів і відповідей.

RewritePath=/inventory/(?.*), /${remaining}:
Фільтр, що переписує шлях запиту. У цьому випадку частина шляху /inventory/ буде видалена із запиту перед його надсиланням в inventory-service. Решта шляху буде збережена і передана далі.

Що таке Service Discovery

Service Discovery (виявлення сервісів) — це механізм, який дозволяє мікросервісам знаходити один одного та взаємодіяти між собою в розподіленій системі без необхідності явного зазначення мережевих адрес. У сучасних мікросервісних архітектурах, де кількість сервісів може бути дуже великою, керування їхніми адресами вручну стає складним і незручним. Service Discovery автоматизує процес знаходження сервісів і спрощує масштабування та керування системою.

Основні компоненти Service Discovery:

Сервіс-реєстратор (Service Registry):
Сервіс-реєстратор — це центральне сховище, де всі доступні сервіси реєструються зі своїми метаданими, такими як IP-адреси, порти та стани. Приклади сервіс-реєстраторів включають Consul, Eureka, Zookeeper та Etcd.

Клієнти сервісів (Service Clients):
Клієнти сервісів — це компоненти, які взаємодіють із сервіс-реєстратором для реєстрації, оновлення та пошуку сервісів.

Механізми перевірки стану (Health Checks):
Механізми перевірки стану використовуються для моніторингу доступності та працездатності сервісів. Сервіси регулярно надсилають інформацію про свій стан у сервіс-реєстратор, щоб підтримувати актуальність даних.

Види Service Discovery:

Клієнтське виявлення (Client-Side Discovery):
У цьому підході клієнтські застосунки самі запитують сервіс-реєстратор для отримання адрес доступних сервісів. Клієнтське виявлення зазвичай реалізується за допомогою бібліотек, інтегрованих у клієнтські застосунки.
Приклад:
Netflix Eureka
Consul

Серверне виявлення (Server-Side Discovery):
У цьому підході клієнтські застосунки надсилають запити на серверний проксі або шлюз (наприклад, API Gateway), який взаємодіє із сервіс-реєстратором і перенаправляє запити на потрібні сервіси.
Приклад:
AWS Elastic Load Balancer (ELB)
Kubernetes Service

Які є інструменти для спостереження за мікросервісами

Prometheus:
Система моніторингу та алертингу з потужною мовою запитів.

Grafana:
Платформа для візуалізації метрик і створення дашбордів.

Elasticsearch, Logstash, Kibana (ELK Stack):
Рішення для централізованого логування та аналізу даних.

Jaeger, Zipkin:
Інструменти для розподіленого трасування.

Як використовувати Elasticsearch у Spring

Elasticsearch — це розподілена пошукова та аналітична система з відкритим кодом, розроблена для зберігання, пошуку та аналізу великих обсягів даних у реальному часі. Elasticsearch часто використовується для повнотекстового пошуку, логів та аналітики.

dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-elasticsearch")
}
spring.elasticsearch.uris=http://localhost:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=your_password
import org.springframework.data.annotation.Id
import org.springframework.data.elasticsearch.annotations.Document
import org.springframework.data.elasticsearch.annotations.Field
@Document(indexName = "products")
data class Product(
@Id
val id: String? = null,
val name: String,
val description: String,
val price: Double,
@Field(type = Date)
val date: String;
)
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository
import org.springframework.stereotype.Repository
@Repository
interface ProductRepository : ElasticsearchRepository<Product, String> {
fun findByName(name: String): List<Product>
}
import org.springframework.stereotype.Service
@Service
class ProductService(private val productRepository: ProductRepository) {
fun findAllProducts(): List<Product> {
return productRepository.findAll().toList()
}
fun saveProduct(product: Product): Product {
return productRepository.save(product)
}
fun findProductByName(name: String): List<Product> {
return productRepository.findByName(name)
}
}
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/products")
class ProductController(private val productService: ProductService) {
@GetMapping
fun getAllProducts(): List<Product> {
return productService.findAllProducts()
}
@PostMapping
fun createProduct(@RequestBody product: Product): Product {
return productService.saveProduct(product)
}
@GetMapping("/search")
fun searchProductsByName(@RequestParam name: String): List<Product> {
return productService.findProductByName(name)
}
}

Що таке Trace ID і Span ID

Trace ID і Span ID — це ключові компоненти розподіленого трасування, які використовуються для моніторингу та налагодження розподілених систем. Вони допомагають відстежувати та пов'язувати запити, що проходять через безліч сервісів, що дозволяє зрозуміти та діагностувати поведінку системи.

Trace ID:
це унікальний ідентифікатор, який присвоюється кожному запиту, що проходить через систему. Цей ідентифікатор залишається незмінним протягом усього життєвого циклу запиту і використовується для пов'язування всіх операцій, пов'язаних із цим запитом.

Мета:
Trace ID дозволяє об'єднати всі спани (частини запиту) в один загальний слід, щоб можна було бачити повний шлях запиту через усі мікросервіси та компоненти системи.

Span ID:
це унікальний ідентифікатор для конкретної операції або частини запиту. Кожен спан є окремим етапом або сегментом запиту, наприклад, виклик методу або виконання запиту до бази даних.

Мета:
Span ID дозволяє ідентифікувати та відстежувати окремі операції всередині одного запиту, надаючи детальну інформацію про кожен крок.

Приклад використання Trace ID і Span ID:
Розгляньмо, як Trace ID і Span ID можуть використовуватися у Spring Boot застосунку за допомогою бібліотеки Spring Cloud Sleuth, яка інтегрується із Zipkin для розподіленого трасування.

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.cloud:spring-cloud-starter-sleuth")
implementation("org.springframework.cloud:spring-cloud-starter-zipkin")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:2020.0.4")
}
}
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.probability=1.0
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.client.RestTemplate
@RestController
class TraceController(
@Autowired val restTemplate: RestTemplate
) {
@GetMapping("/trace")
fun trace(): String {
val response = restTemplate.getForObject(
"http://localhost:8081/external",
String::class.java
)
return "Trace ID and Span ID example: $response"
}
@GetMapping("/external")
fun external(): String {
return "External service response"
}
}

Запустіть Zipkin за допомогою Docker:

docker run -d -p 9411:9411 openzipkin/zipkin

Запустіть ваш Spring Boot застосунок, і він буде автоматично генерувати Trace ID і Span ID для всіх запитів.

Перегляд трасування:
Перейдіть на http://localhost:9411 для відкриття інтерфейсу Zipkin.
Надішліть GET запит на http://localhost:8080/trace.\ Поверніться в інтерфейс Zipkin і знайдіть слід, пов'язаний з вашим запитом. Ви побачите Trace ID і Span ID, що відображають шлях запиту через ваші сервіси.

Copyright: Roman Kryvolapov