JavaRush /Курси /Модуль 5. Spring /Лекція 170: Основні виклики мікросервісів: консистентніст...

Лекція 170: Основні виклики мікросервісів: консистентність даних, складність моніторингу, відмовостійкість

Модуль 5. Spring
Рівень 11 , Лекція 9
Відкрита

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


Консистентність даних у мікросервісах

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

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

Тут вже не можна просто сказати: "Гаразд, я виконаю все в одній транзакції". Ні, друже мій, розподілена система вимагає складнішого підходу!

Рішення проблеми

Розподілені транзакції можна реалізувати за допомогою патерна Sagas. Суть його така: кожен сервіс виконує свою частину роботи і повідомляє іншим про успішне завершення. Якщо щось іде не так, запускається компенсувальна операція (відкат).

Діаграма роботи Sagas:


+-------------------+             +-------------------+
| Order Service     |  -------->  | Payment Service   |
+-------------------+             +-------------------+
        |                               |
        |        [Success]              |       [Success]
        |                               |
+-------------------+          +-------------------+
| Inventory Service |  <-------| Notification Svc |
+-------------------+          +-------------------+

На практиці:

  1. Оркестрація — один головний компонент керує процесом виконання (наприклад, Saga Orchestrator).
  2. Хореографія — самі сервіси обмінюються подіями, реагуючи на успіх/помилку інших.

Приклад реалізації:


// Приклад оркестрації Sagas у Spring
public class OrderService {
    @Autowired
    private EventPublisher eventPublisher;

    public void createOrder(Order order) {
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
    }

    @EventListener
    public void onPaymentSuccessful(PaymentCompletedEvent e) {
        eventPublisher.publishEvent(new InventoryReservedEvent(e.getOrderId()));
    }

    @EventListener
    public void onInventoryFailure(InventoryFailureEvent e) {
        // Компенсація: скасовуємо замовлення
        eventPublisher.publishEvent(new OrderCancelledEvent(e.getOrderId()));
    }
}

Інший підхід до консистентності — принцип консистентності в кінцевому підсумку (Eventual Consistency). Він працює так: зміни розповсюджуються системою через події, і все врешті-решт приходить до узгодженого стану (наче "майже одразу").

Можеш використовувати брокери повідомлень на кшталт Kafka для передачі таких подій між мікросервісами.


Складність моніторингу

Якщо ти думав, що моніторинг монолітного застосунку складний, то послухай це: у системі з десятків (або сотень!) мікросервісів усе стає значно складнішим. Знайти, де саме "болить", буває справжньою головоломкою.

Чому моніторинг складний?

  1. Велика кількість точок відмови. В одному моноліті часто є одна база даних, один сервер. У мікросервісній системі таких точок сотні.
  2. Розподілена архітектура. Сервіси взаємодіють через мережу, і відлагодження помилок ускладнюється: "А це помилка сервісу чи мережі?".
  3. Асинхронність. Можуть виникати затримки в обробці подій і непередбачувана поведінка.

Рішення проблеми

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

Налаштування Sleuth і Zipkin:


spring:
  sleuth:
    sampler:
      probability: 1.0
zipkin:
  base-url: http://localhost:9411

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

Інструменти на кшталт Prometheus і Grafana допомагають відстежувати метрики (наприклад, продуктивність, завантаження пам'яті) в реальному часі. Ти також можеш додати кастомні метрики через Spring Actuator.

Приклад кастомної метрики:


@Component
public class CustomMetrics {
    private final Counter myCounter;

    public CustomMetrics(MeterRegistry registry) {
        this.myCounter = Counter.builder("custom.metric")
            .description("Example custom metric")
            .register(registry);
    }

    public void increment() {
        myCounter.increment();
    }
}

Відмовостійкість у мікросервісах

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

Проблеми відмов

  1. Ланцюгові реакції відмов. Один сервіс падає — за ним може впасти вся система.
  2. Повільні відповіді. Якщо якийсь сервіс "гальмує", він може уповільнити весь ланцюг.

Рішення проблеми

Патерн Circuit Breaker запобігає ланцюговим реакціям відмов. Якщо сервіс перестає відповідати належним чином, Circuit Breaker розриває зв'язок і повертає попередньо визначену відповідь (або зовсім нічого). Після короткого періоду він знову перевіряє стан сервісу.

Реалізація з Resilience4j:


@Retry(name = "myService", fallbackMethod = "fallback")
public String callExternalService() {
    // Логіка виклику зовнішнього сервісу
}

public String fallback(Exception e) {
    return "Fallback response";
}

Інший прийом — це Fallback: якщо сервіс недоступний, повертай попередньо закешовану відповідь. Також обмежуй час очікування запитів (timeouts).

Spring Cloud Retry:


spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true

У реальному житті

Усе, що ми сьогодні обговорювали, надзвичайно важливо для реальної розробки. Проблеми консистентності даних виникають, коли хочеш зберегти одночасно замовлення і оплату, а моніторинг з розподіленою трасировкою стає найкращим другом, коли щось "ламається". Circuit Breaker — твій рятівник від ланцюгових реакцій, а Fallbacks і Retry запобігають катастрофічним наслідкам.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ