Java Spring — Мікросервіси
Як можуть взаємодіяти мікросервіси
У мікросервісній архітектурі мікросервіси можуть взаємодіяти один з одним кількома способами, такими як синхронні та асинхронні виклики. Розгляньмо основні підходи до взаємодії мікросервісів:
Синхронні виклики (HTTP/REST):
Це найпоширеніший спосіб взаємодії мікросервісів. Один мікросервіс робить HTTP-запит до іншого мікросервісу. У Spring Boot це можна реалізувати за допомогою RestTemplate або WebClient.
Приклад використання RestTemplate:
import org.springframework.stereotype.Serviceimport org.springframework.web.client.RestTemplate
@Serviceclass 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.Serviceimport org.springframework.web.reactive.function.client.WebClient
@Serviceclass 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.Queueimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configuration
@Configurationclass RabbitMQConfig {
@Bean fun queue(): Queue { return Queue("example.queue", false) }
}import org.springframework.amqp.rabbit.core.RabbitTemplateimport org.springframework.stereotype.Component
@Componentclass MessageSender(private val rabbitTemplate: RabbitTemplate) {
fun sendMessage(message: String) { rabbitTemplate.convertAndSend("example.queue", message) }
}import org.springframework.amqp.rabbit.annotation.RabbitListenerimport org.springframework.stereotype.Component
@Componentclass MessageReceiver {
@RabbitListener(queues = ["example.queue"]) fun receiveMessage(message: String) { println("Received message: $message") }
}Взаємодія через базу даних:
Мікросервіси можуть взаємодіяти через спільну базу даних, однак це не рекомендується, оскільки порушує принцип ізоляції даних у мікросервісній архітектурі. Краще використовувати базу даних лише для зберігання даних, а взаємодію здійснювати через API.
Використання сервісів виявлення та маршрутизації:
Eureka (виявлення сервісів)
Приклад конфігурації Eureka:
import org.springframework.beans.factory.annotation.Autowiredimport org.springframework.cloud.client.discovery.DiscoveryClientimport org.springframework.stereotype.Serviceimport org.springframework.web.client.RestTemplate
@Serviceclass 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.StreamObserverimport net.devh.boot.grpc.server.service.GrpcService
@GrpcServiceclass 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.GrpcClientimport org.springframework.stereotype.Service
@Serviceclass 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/datavolumes: 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 buildWORKDIR /app# Копіюємо файли збірки Gradle і вихідний кодCOPY build.gradle settings.gradle gradlew gradlew.bat ./COPY gradle gradleCOPY src src# Встановлюємо дозволи на виконання файлу gradlewRUN chmod +x ./gradlew# Завантажуємо залежності і збираємо проєктRUN ./gradlew bootJar# Етап 2: Створення мінімального образу для запуску застосункуFROM openjdk:21-jdk-slimWORKDIR /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 logindocker 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/mydatabasespring.datasource.username=postgresspring.datasource.password=postgresspring.jpa.hibernate.ddl-auto=updateСтворення Dockerfile:
# Use the official image as a parent imageFROM openjdk:11-jre-slim# Set the working directory in the containerWORKDIR /app# Copy the jar file to the containerCOPY target/demo-0.0.1-SNAPSHOT.jar app.jar# Run the jar fileENTRYPOINT ["java", "-jar", "app.jar"]Створіть файл docker-compose.yml у кореневій директорії проєкту:
version: '3.8' # Вказує версію формату файлу Docker Composeservices: # Визначає список сервісів, які будуть розгорнуті 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 # Створення тому для зберігання даних PostgreSQLvolumes: # Визначає список томів, які будуть використовуватися сервісами 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 imageFROM openjdk:11-jre-slim# Set the working directory in the containerWORKDIR /app# Copy the jar file to the containerCOPY target/demo-0.0.1-SNAPSHOT.jar app.jar# Run the jar fileENTRYPOINT ["java", "-jar", "app.jar"]Зберіть Docker-образ і завантажте його в Docker Hub (або будь-який інший реєстр Docker-образів).
# Збірка Docker-образуdocker build -t your-dockerhub-username/demo .# Вхід у Docker Hubdocker login# Завантаження Docker-образу в Docker Hubdocker push your-dockerhub-username/demoСтворення Kubernetes маніфестів:
Створіть маніфести Kubernetes для розгортання, служби та конфігурації Ingress.
deployment.yaml:
apiVersion: apps/v1kind: Deploymentmetadata: name: demo-deployment labels: app: demospec: replicas: 3 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - name: demo image: your-dockerhub-username/demo:latest ports: - containerPort: 8080service.yaml:
apiVersion: v1kind: Servicemetadata: name: demo-servicespec: selector: app: demo ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalanceringress.yaml:
apiVersion: networking.k8s.io/v1kind: Ingressmetadata: 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.yamlkubectl apply -f service.yamlkubectl apply -f ingress.yamlНалаштування DNS:
Для локальної розробки з Minikube додайте наступний запис у файл /etc/hosts:
<MINIKUBE_IP> demo.localОтримайте IP Minikube за допомогою команди:
minikube ipПеревірка розгортання:
Перевірте стан вашого розгортання, служби та ingress за допомогою команд:
kubectl get deploymentskubectl get serviceskubectl 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:9092spring.kafka.consumer.group-id=my-groupspring.kafka.consumer.auto-offset-reset=earliestspring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializerspring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializerspring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializerspring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializerабо через клас конфігурації
import org.apache.kafka.clients.producer.ProducerConfigimport org.apache.kafka.common.serialization.StringSerializerimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.kafka.core.DefaultKafkaProducerFactoryimport org.springframework.kafka.core.KafkaTemplateimport org.springframework.kafka.core.ProducerFactoryimport org.springframework.kafka.support.serializer.JsonSerializer
@Configurationclass 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.ConsumerConfigimport org.apache.kafka.common.serialization.StringDeserializerimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configurationimport org.springframework.kafka.annotation.EnableKafkaimport org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactoryimport org.springframework.kafka.core.ConsumerFactoryimport org.springframework.kafka.core.DefaultKafkaConsumerFactoryimport org.springframework.kafka.support.serializer.ErrorHandlingDeserializerimport org.springframework.kafka.support.serializer.JsonDeserializer
@EnableKafka@Configurationclass 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.KafkaTemplateimport org.springframework.stereotype.Service
@Serviceclass EventProducer(private val kafkaTemplate: KafkaTemplate<String, String>) {
fun sendMessage(topic: String, message: String) { kafkaTemplate.send(topic, message) }
}import org.springframework.kafka.annotation.KafkaListenerimport org.springframework.stereotype.Service
@Serviceclass 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.Queueimport org.springframework.amqp.rabbit.connection.ConnectionFactoryimport org.springframework.amqp.rabbit.core.RabbitTemplateimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configuration
@Configurationclass RabbitConfig {
@Bean fun queue(): Queue { return Queue("myQueue", false) }
@Bean fun rabbitTemplate(connectionFactory: ConnectionFactory): RabbitTemplate { return RabbitTemplate(connectionFactory) }
}import org.springframework.amqp.rabbit.core.RabbitTemplateimport org.springframework.stereotype.Service
@Serviceclass MessageSender(private val rabbitTemplate: RabbitTemplate) {
fun sendMessage(message: String) { rabbitTemplate.convertAndSend("myQueue", message) }
}import org.springframework.amqp.rabbit.annotation.RabbitListenerimport org.springframework.stereotype.Service
@Serviceclass MessageListener {
@RabbitListener(queues = ["myQueue"]) fun receiveMessage(message: String) { println("Received message: $message") }
}import org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RequestParamimport org.springframework.web.bind.annotation.RestController
@RestControllerclass 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=8080eureka.client.register-with-eureka=falseeureka.client.fetch-registry=falseeureka.server.enable-self-preservation=falseserver.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.SpringBootApplicationimport org.springframework.boot.runApplicationimport org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
@SpringBootApplication@EnableEurekaServerclass 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=0eureka.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.SpringBootApplicationimport org.springframework.boot.runApplicationimport org.springframework.cloud.netflix.eureka.EnableEurekaClient
@SpringBootApplication@EnableEurekaClientclass EurekaClientApplication
fun main(args: Array<String>) { runApplication<EurekaClientApplication>(*args)}import org.springframework.cloud.client.discovery.DiscoveryClientimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestController
@RestControllerclass 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.SpringBootApplicationimport org.springframework.boot.runApplicationimport org.springframework.cloud.client.loadbalancer.LoadBalancedimport org.springframework.context.annotation.Beanimport org.springframework.web.client.RestTemplate
@SpringBootApplicationclass 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=8080spring.cloud.gateway.discovery.locator.enabled=truespring.cloud.gateway.discovery.locator.lower-case-service-id=trueeureka.client.service-url.defaultZone=http://localhost:8761/eureka/api-gateway/src/main/kotlin/com/example/apigateway/ApiGatewayApplication.kt
import org.springframework.boot.autoconfigure.SpringBootApplicationimport org.springframework.boot.runApplicationimport org.springframework.cloud.client.discovery.EnableDiscoveryClient
@SpringBootApplication@EnableDiscoveryClientclass 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=8080spring.cloud.gateway.discovery.locator.enabled=truespring.cloud.gateway.discovery.locator.lower-case-service-id=trueeureka.client.service-url.defaultZone=http://localhost:8761/eureka/# Визначення маршрутів для eureka-clientspring.cloud.gateway.routes[0].id=eureka-clientspring.cloud.gateway.routes[0].uri=lb://eureka-clientspring.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# Вмикаємо автоматичне виявлення маршрутів для сервісів, зареєстрованих в Eurekaspring.cloud.gateway.discovery.locator.enabled=truespring.cloud.gateway.discovery.locator.lower-case-service-id=true# Вказуємо URL Eureka Server для реєстрації та виявлення сервісівeureka.client.service-url.defaultZone=http://localhost:8761/eureka/# Визначення маршрутів для order-servicespring.cloud.gateway.routes[0].id=order-servicespring.cloud.gateway.routes[0].uri=lb://order-servicespring.cloud.gateway.routes[0].predicates[0]=Path=/orders/**spring.cloud.gateway.routes[0].filters[0]=RewritePath=/orders/(?<remaining>.*), /${remaining}# Визначення маршрутів для inventory-servicespring.cloud.gateway.routes[1].id=inventory-servicespring.cloud.gateway.routes[1].uri=lb://inventory-servicespring.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:9200spring.elasticsearch.username=elasticspring.elasticsearch.password=your_passwordimport org.springframework.data.annotation.Idimport org.springframework.data.elasticsearch.annotations.Documentimport 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.ElasticsearchRepositoryimport org.springframework.stereotype.Repository
@Repositoryinterface ProductRepository : ElasticsearchRepository<Product, String> {
fun findByName(name: String): List<Product>
}import org.springframework.stereotype.Service
@Serviceclass 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:9411spring.sleuth.sampler.probability=1.0import org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestControllerimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.web.client.RestTemplate
@RestControllerclass 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, що відображають шлях запиту через ваші сервіси.