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

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

Открыть все вопросы по Spring

В этой статье:

➤ Как могут взаимодействовать микросервисы
➤ Как использовать Docker в Spring
➤ Как использовать Docker Compose в Spring
➤ Как использовать Kubernetes в Spring
➤ Как использовать Elasticsearch в Spring
➤ Как использовать RabbitMQ в Spring
➤ Как использовать Kafka в Spring
➤ Что такое Eureka Service
➤ Как реализовать Spring Cloud Gateway
➤ Как реализовать перенаправление при помощи Spring Cloud Gateway
➤ Что такое Service Discovery
➤ Что такое Dockerfile и Docker Compose
➤ Какие есть инструменты для наблюдения за микросервисами
➤ Что такое Trace ID и Span ID

В микросервисной архитектуре микросервисы могут взаимодействовать друг с другом несколькими способами, такими как синхронные и асинхронные вызовы. Рассмотрим основные подходы к взаимодействию микросервисов:

Синхронные вызовы (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-образов. Это основной способ описания, как построить образ контейнера с нужными зависимостями и конфигурациями.

# Использование базового образа
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 — это платформа для разработки, доставки и запуска приложений в контейнерах. Контейнеры позволяют упаковать приложение с его зависимостями и обеспечить его изоляцию и портативность.

Основные концепции 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 — это инструмент для определения и управления многоконтейнерными 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 — это система оркестрации контейнеров с открытым исходным кодом, которая позволяет автоматизировать развертывание, масштабирование и управление контейнеризованными приложениями. Она обеспечивает платформу для запуска контейнеров, их управления и масштабирования.

Вот пошаговое руководство по использованию 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 — это распределенная стриминговая платформа, которая используется для построения систем обработки данных в реальном времени. 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 — это программное обеспечение для очередей сообщений (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 — это две популярные системы обмена сообщениями, которые используются для передачи сообщений между компонентами распределенных систем. Однако они имеют разные архитектурные подходы, целевые сценарии использования и особенности.

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 — это часть набора инструментов для разработки микросервисов от 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 — это современный шлюз 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.

Предположим, у нас есть два микросервиса, зарегистрированные в 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 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 — это распределенная поисковая и аналитическая система с открытым исходным кодом, разработанная для хранения, поиска и анализа больших объемов данных в реальном времени. 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:
это уникальный идентификатор, который присваивается каждому запросу, проходящему через систему. Этот идентификатор остается неизменным на протяжении всего жизненного цикла запроса и используется для связывания всех операций, связанных с этим запросом.

Цель:
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