Микросервисы сына маминой подруги. Пишем правильные микросервисные приложения на Java

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! Сегодня я решил написать статейку про всеми любимые микросервисы. Если вы давно хотели изучить тему микросервисных приложении и актуальных технологии, которые используются для их построения, то данная статья должна вам помочь в этом.

Что такое "микросервисы"

Микросервисы - это архитектурный подход к разработке приложений, который предполагает разбиение приложения на маленькие и независимые компоненты, каждый из которых выполняет определенную функцию. Эти компоненты могут взаимодействовать друг с другом через API, что позволяет разрабатывать приложения более гибко и масштабируемо.

Если говорить более простым языком, микросервисы - это как Lego для программистов. Вы просто собираете разные кирпичики вместе, чтобы создать красивое здание, а если что-то сломается, то можно легко заменить только нужный кирпичик, не перестраивая всю конструкцию. И да, приятно наступать на микросервисы, потому что они маленькие и не так болят, как большие монолиты!

Автор статьи когда придумал аналогию с лего
Автор статьи когда придумал аналогию с лего

Микросервисная архитектура стала такой актуальной, потому что она предоставляет ряд преимуществ, которые важны для современной разработки программного обеспечения:

  1. Гибкость и масштабируемость: микросервисы позволяют быстро и гибко развивать приложения, добавляя новые функции и компоненты. Они также обеспечивают легкую масштабируемость приложений при необходимости.

  2. Разделение ответственности: каждый микросервис выполняет определенную функцию, что позволяет разделять ответственность между разными командами и упрощает сопровождение приложения.

  3. Независимость и устойчивость к ошибкам: каждый микросервис работает независимо от других, что позволяет изолировать ошибки и сбои в работе одного компонента, не затрагивая работу всего приложения.

  4. Легкая замена и обновление: микросервисы легко заменять и обновлять, что упрощает поддержку приложения и уменьшает время простоя в случае сбоев или изменений.

  5. Подход к использованию современных технологий: микросервисы позволяют использовать различные технологии и языки программирования для каждого компонента, что упрощает разработку и повышает производительность. (Но запомните, лучше Java ничего нет)

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

Как дела у Java с микросервисами

Сегодня Spring Boot предоставляет огромный пакет инструментов для работы с микросервисами, подробнее с этим топиком вы можете ознакомиться по этой ссылке, ну а мы пойдем дальше и рассмотрим всеми любимый Spring Cloud.

Spring Cloud - это набор инструментов, разработанных компанией Spring, которые позволяют быстро и легко создавать микросервисные приложения. Он обеспечивает множество функций, таких как конфигурация, регистрация и отслеживание микросервисов, балансировка нагрузки, трассировка запросов и многое другое.

Для понимания, насколько важен Spring Cloud, представьте себе, что вы пытаетесь создать микросервисную архитектуру без него. Вы должны были бы написать свой собственный код для каждой из функций, таких как конфигурация, регистрация и отслеживание микросервисов, балансировка нагрузки, и так далее. Это занимает много времени и усилий, и может привести к ошибкам, которые можно было бы избежать с помощью Spring Cloud.

API Gateway и Сервер Реестра Сервисов

Архитектура микросервисных приложении на самом деле очень простая и крайне действенная. Главная мысль такой архитектуры состоит в инкапсуляций наших сервисов от запросов из вне. Для коммуникации с нашими сервисами всегда поднимается отдельный API Gateway.

API Gateway - это как дверь в космический корабль. Он предоставляет доступ к различным отсекам корабля, но также управляет входящим и исходящим трафиком, чтобы защитить экипаж от космических опасностей. И когда капитан корабля попросит открыть дверь, API Gateway будет готов выполнить свою задачу и позволить ему получить доступ к нужному отсеку!

Но не все так просто как может казаться на первый взгляд. В первую очередь, для того, чтобы ваш Gateway работал правильно вам нужно иметь tool регистрации ваших микросервисов, иначе gateway не будет знать куда нужно отправлять те или иные запросы. Для этого в Java есть Spring Cloud Netflix в котором есть прекрасная Eureka.

Spring Cloud Eureka - это сервер реестра сервисов, который используется для обнаружения и регистрации микросервисов в распределенной системе. Он является частью библиотеки Spring Cloud и предоставляет механизмы для управления и мониторинга состояния сервисов в системе.

Принцип работы Spring Cloud Eureka достаточно прост: каждый микросервис регистрирует себя на сервере Eureka при запуске, сообщая о своем имени, IP-адресе и порте. Затем клиенты могут использовать Eureka для поиска нужных сервисов и получения их адресов.

То есть, мы настраиваем наш gateway таким образом, чтобы он мог трекать наш реестр сервисов, брать оттуда нужную информацию о микросервисе и перенаправлять туда наши запросы. Давайте разберем следующий пример:

Gateway настраивается крайне просто, для этого сначала нам нужно добавить 2 dependency в наш Java проект:

pom.xml:

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

gradle.build:

implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-gateway'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-client'

Далее, мы должны пометить наши Application файлы аннотацией @EnableEurekaClient для регистрации проектов как микросервисы. (Это нужно сделать как для ApiGateway так и для любого другого микросервиса).

Последним шагом остается настройка application.yml или .properties файла следующим образом. Для примера предположим, что у нас есть 2 сервиса куда нужно переадресовывать запросы: Incident-Service и Authentication-Service.

server:
  port: 8083

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: incidents
          uri: lb://INCIDENTS
          predicates:
            - Path=/api/v1/incident/**
        - id: authentication
          uri: lb://AUTHENTICATION
          predicates:
            - Path=/api/v1/authentication/**


eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
    fetch-registry: true
    register-with-eureka: true

id и uri генерятся в зависимости от поля spring.application.name в вашем конфигурационном файле.

Общение Сервисов

Ну вот мы и пришли к нашим любимым брокерам сообщений. Я их называю страшным сном Junior программистов, потому что когда на собесах они слышат такие слова как "Message Queue", "Kafka" и "RabbitMQ" то их лицо покрывается мокрым потом.

Таска на собесе для Джуна: Напиши мне копию Спринг бута за 15 минут и покрой весь код юнит тестами.
Таска на собесе для Джуна: Напиши мне копию Спринг бута за 15 минут и покрой весь код юнит тестами.

На самом деле брокеры сообщений очень крутая и простая штука, которая позволяет оптимизировать работу микросервисных приложении.

Message queue - это механизм, который позволяет компонентам системы обмениваться сообщениями друг с другом через посредника, называемого очередью сообщений. Очередь сообщений является центральным местом для хранения и передачи сообщений между компонентами системы.

Если представить себе систему как ресторан, то очередь сообщений - это как раз та очередь, которая складывается перед самым популярным блюдом в меню. Как только заказы на это блюдо начинают поступать, официант ставит их в очередь, а повар начинает готовить их по очереди. Таким образом, каждый посетитель получает свою порцию блюда в порядке очереди, без хаоса и конфликтов.

Однако, также как и в ресторане, очередь сообщений может переполниться, если в системе постоянно поступают большие объемы запросов или компоненты не успевают обрабатывать сообщения быстро. И в этом случае, как и в ресторане, может произойти настоящий "фуд-файт", который приведет к сбою в работе системы.

Так что, если вы решите использовать message queue, не забывайте следить за ее объемами и убедитесь, что все компоненты системы работают в режиме "один за другим". И тогда ваша система будет функционировать как швейцарские часы и каждый посетитель будет получать свой заказ вовремя и без задержек!

У многих всегда возникает один и тот же вопрос: "Зачем мне использовать брокер сообщений для общения между микросервисами? Я могу отправлять запросы простым restTemplate и не парить себе мозги". На этот вопрос я всегда люблю отвечать примерно в таком ключе:

Использование брокера сообщений между микросервисами имеет несколько преимуществ, которые делают его важным элементом в микросервисной архитектуре.

  1. Разрыв зависимостей: Брокер сообщений позволяет уменьшить зависимость между микросервисами, что делает систему более гибкой и масштабируемой. Микросервисы могут обмениваться сообщениями через брокер, не имея непосредственного знания друг о друге, что позволяет легко добавлять, удалять и изменять микросервисы.

  2. Распределение нагрузки: Брокер сообщений может обрабатывать сообщения асинхронно, что позволяет распределить нагрузку между микросервисами и обеспечить более быструю обработку запросов.

  3. Гарантированная доставка: Брокер сообщений обеспечивает гарантированную доставку сообщений, даже если один из микросервисов временно недоступен или отказался обрабатывать сообщение.

  4. Улучшенная отказоустойчивость: Если один из микросервисов временно недоступен или вышел из строя, брокер сообщений может сохранить сообщения в очереди и отправить их, когда микросервис снова станет доступен.

Таким образом, использование брокера сообщений между микросервисами позволяет обеспечить более гибкую, масштабируемую и отказоустойчивую систему. Это как занесение банкира между игроками в настольный футбол - он обеспечивает правильное распределение мяча и гарантирует, что игра будет проходить справедливо и без обидных проигрышей.

На рынке сегодня есть 2 основных open-source решения по брокерам сообщении. Это всеми любимые Kafka и RabbitMQ. Подробнее об их отличиях вы можете почитать в этой прекрасной статье на хабре ну а мы пойдем дальше и рассмотрим простые кейсы настройки брокеров в Java Spring Boot Applications.

Давайте разберем такой use-case:

У нас есть 2 сервиса:

  • Incident Service

  • Email Notification Service

Оба микросервиса ранятся на одном сервере, но на разных хостах. Наша задача состоит в написание метода, который будет создавать новый инцидент и отправлять какую-то информацию на сервис email-notification.

Для начала запомните, что когда вы работаете с брокером сообщения у вас есть 3 основных понятия:

  • Producer - отправитель

  • Consumer - получатель

  • Topic - топик вашего приложения

Для правильной работы вашего брокера сообщении вы должны их правильно сконфигурировать в вашем Java Application.

KafkaConsumerConfig:

@Configuration
@RequiredArgsConstructor
public class KafkaConsumerConfig {

    @Value("localhost:29092")
    private String bootstrapService;


    public Map<String, Object> consumerConfig() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapService);
        return props;
    }

    @Bean
    public ConsumerFactory<String, EmailNotificationDtoRequest> consumerFactory() {
        // EmailNotificationDtoRequest DTO который наш consumer будет принимать
        JsonDeserializer<EmailNotificationDtoRequest> deserializer =new JsonDeserializer<>();
        deserializer.addTrustedPackages("*");
        return new DefaultKafkaConsumerFactory<>(consumerConfig(),new StringDeserializer(), deserializer);
    }

    @Bean
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, EmailNotificationDtoRequest>> factory(ConsumerFactory<String, EmailNotificationDtoRequest> consumerFactory) {
        ConcurrentKafkaListenerContainerFactory<String, EmailNotificationDtoRequest> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory);
        return factory;
    }
}

KafkaProducerConfig:

@Configuration
public class KafkaProducerConfig {

    @Value("localhost:29092")
    private String bootstrapService;


    public Map<String, Object> producerConfig() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapService);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        return props;
    }

    @Bean
    public ProducerFactory<String, EmailNotificationDtoRequest> producerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfig());
    }

    @Bean
    public KafkaTemplate<String, EmailNotificationDtoRequest> kafkaTemplate(ProducerFactory<String, EmailNotificationDtoRequest> producerFactory) {
        return new KafkaTemplate<>(producerFactory);
    }

}

KafkaTopicConfig:

@Configuration
public class KafkaTopicConfig {

    @Bean
    public NewTopic gilgameshNotificationTopic() {
        return TopicBuilder.name("gilgamesh-notification")
                .build();
    }
}

Главное не пугайтесь страшных слов "Сериалайзеры" И "Десериалайзеры" в них мы просто указываем тип передачи данных между сервисами.

Мок реализация нашего метода:

@Override
    @Transactional
    public Incident create(IncidentDtoRequest dtoRequest) {
        Incident incident;
        try {
            incident = new Incident();
            incident.setCategory(categoryService.getByIdThrowException(dtoRequest.getCategoryId()));
            incident.setDescription(dtoRequest.getDescription());
            incident.setPriorityLevel(priorityLevelService.getByIdThrowException(dtoRequest.getPriorityLevelId()));
            incident.setTitle(dtoRequest.getTitle());
            incident.setTags(tagService.getAllByListOfIds(dtoRequest.getTagIds()));
            incident.setTypes(typeService.getAllByListOfIds(dtoRequest.getTypeIds()));

            dtoRequest.getUserIds().forEach(ids -> incidentUsersService.create(incident.getId(), ids));

            EmailNotificationDtoRequest emailNotificationDtoRequest = new EmailNotificationDtoRequest();
            emailNotificationDtoRequest.setEmailType(1L);
            emailNotificationDtoRequest.setDescription("Вы создали новый инцидент под названием: " + dtoRequest.getTitle());
            emailNotificationDtoRequest.setTitle("Создание нового инцидента");
            kafkaTemplate.send("gilgamesh-notification",emailNotificationDtoRequest);

            return this.save(incident);
        }
        catch (Exception e) {
            log.error(e);
            throw new CustomCouldNotCreateException(CustomStatusCode.COULD_NOT_CREATE_RECORD_IN_DB.getCode());
        }
    }

Здесь мы можем наблюдать, как мы сначала создаем инцидент а потом при помощи kafkaTemplate отправляем запрос на топик "gilgamesh-notification".

Осталось только настроить KafkaListener на нашем email-notification-service.

KafkaListeners:

@Component
@RequiredArgsConstructor
public class KafkaListeners {

    private final EmailNotificationService emailNotificationService;

    @KafkaListener(topics = "gilgamesh-notification", groupId = "groupId", containerFactory = "factory")
    void listener(EmailNotificationDtoRequest dtoRequest) {
        System.out.println("Listener recieved data: " + dtoRequest);
        emailNotificationService.create(dtoRequest);
    }
}

Поздравляю, вы официально теперь Senior программист, который умеет работать с Брокерами Сообщений(нет).

Трассировка Запросов

Трассировка запросов в микросервисной архитектуре - это процесс отслеживания запросов, проходящих через различные микросервисы в системе, с целью обнаружения проблем и улучшения производительности системы в целом.

Когда пользователь отправляет запрос к системе, он может проходить через несколько микросервисов, каждый из которых выполняет свою часть работы. При трассировке запросов каждый микросервис записывает информацию о запросе, такую как его идентификатор и время начала и окончания обработки. Эта информация передается следующему микросервису, который добавляет свою информацию к записи. Таким образом, мы можем отследить, как запрос проходит через систему и какие микросервисы на него влияют.

Трассировка запросов позволяет обнаруживать узкие места в системе и находить проблемы производительности. Например, если один микросервис выполняет долгую операцию, это может замедлить всю систему. Трассировка запросов помогает быстро определить эту проблему и принять меры для ее устранения.

Кроме того, трассировка запросов может использоваться для отладки и анализа ошибок в системе. Если запрос не может быть выполнен из-за ошибки в одном из микросервисов, мы можем легко определить, где произошла ошибка и что ее вызвало.

Таким образом, трассировка запросов является важным инструментом для мониторинга и управления микросервисной архитектурой. Это как GPS-навигатор, который помогает водителю определить свое местоположение и выбрать наиболее эффективный маршрут.

Моя любимая связка приложении всегда будет Sleuth+Zipkin.

Sleuth и Zipkin - это две программы, которые используются для трассировки запросов в микросервисной архитектуре.

Sleuth - это библиотека, разработанная компанией Spring, которая автоматически генерирует уникальные идентификаторы запросов и добавляет их в логи каждого микросервиса. Когда запрос проходит через несколько микросервисов, Sleuth позволяет связать все логи вместе и создать полную картину запроса в системе. Это упрощает процесс трассировки запросов и помогает быстрее обнаруживать и исправлять проблемы в системе.

Zipkin - это инструмент для трассировки запросов, который позволяет визуализировать взаимодействие между микросервисами в системе. Он использует информацию, собранную Sleuth, чтобы создать диаграмму запросов, которая показывает, как запросы проходят через различные микросервисы в системе. Это упрощает процесс отслеживания запросов и обнаружения проблем в системе.

Таким образом, Sleuth и Zipkin работают вместе для обеспечения эффективной трассировки запросов в микросервисной архитектуре. Sleuth собирает информацию о запросах в каждом микросервисе и передает ее в Zipkin, который создает диаграмму запросов и помогает быстро находить проблемы в системе.

Для корректной работы данных приложении хватает нескольких зависимостей и строк в application файлах:

pom.xml:

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

gradle.build:

implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-sleuth'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-sleuth-zipkin'

application.yml:

spring:
  application:
    name: "название вашего сервиса"
  zipkin:
    base-url: http://localhost:9411

Всего пар строк кода и вы облегчили жизнь десяткам людей на поддержке!

Заключение

Микросервисы крайне интересный и увлекательный топик. Вы можете играться с настройками брокеров сообщении, api gateway как вам угодно. Но запомните главное правило - микросервисы это не панацея. Иногда монолиты являются более оптимальным решением нежели микросервисы.

Всем добра и позитива! Пользуйтесь Kcell и Activ!

Источник: https://habr.com/ru/post/726672/


Интересные статьи

Интересные статьи

Меня зовут Мария Оборина, я почти пять лет подбирала персонал, была карьерным консультантом, экспертом в HH.ru для соискателей. Уже полтора года я в Практикуме — отвечаю за карьерные треки курсов, в т...
Это руководство поможет вам понять, что представляет собой Project Loom в Java и как его виртуальные потоки (также называемые «fibers») работают «под капотом».
Стандартный способ настроить навигацию в iOS-приложении — использовать класс UIViewController. Он работает, пока не понадобится добавить новые экраны или поменять их местами. Сложную логику переходов ...
Привет, Хаброжители! Хотите сделать отличный подарок ребёнку, желающему научиться программировать, или научить взрослого, далёкого от мира кодов? Тогда книга-героиня нашего поста Вам подойдет. Э...
Автор уже опубликовал скрипт карусели, который также использует только CSS и Javascript. Теперь давайте рассмотрим скрипт слайдера. Он отличается от карусели тем, что одновременно виден только од...