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 за внедряване (Deployment), услуга (Service) и конфигурация на 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): 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=8761eureka.client.register-with-eureka=falseeureka.client.fetch-registry=falseeureka.server.enable-self-preservation=falseserver.port=8761:
Задава порта, на който ще бъде стартиран Eureka Server. По подразбиране Spring Boot стартира приложението на порт 8080, а Eureka Server по принцип се стартира на 8761.
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>.*), /${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>.*), /${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, които показват пътя на заявката през вашите услуги.