Какие ключевые паттерны используются в микросервисной архитектуре?

Ответ

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

  1. API Gateway: Единая точка входа для всех клиентских запросов. Он маршрутизирует запросы к нужным сервисам, а также может выполнять аутентификацию, логирование, кэширование и rate limiting.

  2. Service Discovery: Механизм для автоматического обнаружения сетевого местоположения (IP-адрес и порт) экземпляров сервисов. Позволяет сервисам находить друг друга без жестко прописанных адресов. Примеры: Consul, etcd, Zookeeper.

  3. Circuit Breaker (Предохранитель): Защищает систему от каскадных сбоев. Если сервис-зависимость начинает возвращать ошибки, Circuit Breaker "размыкается" и перестает отправлять на него запросы на некоторое время, возвращая ошибку немедленно. Это дает сбойному сервису время на восстановление.

  4. Database per Service: Каждый микросервис управляет своей собственной базой данных. Это обеспечивает слабую связанность (loose coupling), так как изменения в схеме данных одного сервиса не влияют на другие.

  5. Saga Pattern: Паттерн для управления распределенными транзакциями. Вместо одной большой ACID-транзакции используется последовательность локальных транзакций в каждом сервисе. Если одна из них сбоит, выполняются компенсирующие транзакции для отката изменений.

  6. CQRS (Command Query Responsibility Segregation): Разделение операций на команды (изменение данных) и запросы (чтение данных). Это позволяет независимо масштабировать и оптимизировать модели для чтения и записи.

Пример реализации Circuit Breaker в Go:

import (
    "time"
    "github.com/sony/gobreaker"
)

var cb *gobreaker.CircuitBreaker

func init() {
    settings := gobreaker.Settings{
        Name:        "my-external-service",
        MaxRequests: 1,
        Timeout:     5 * time.Second,
        ReadyToTrip: func(counts gobreaker.Counts) bool {
            return counts.ConsecutiveFailures > 3
        },
    }
    cb = gobreaker.NewCircuitBreaker(settings)
}

func CallMyService() (interface{}, error) {
    body, err := cb.Execute(func() (interface{}, error) {
        // Логика вызова внешнего сервиса
        return externalAPI.GetData()
    })
    if err != nil {
        return nil, err
    }
    return body, nil
}

Ответ 18+ 🔞

А, микросервисы, блядь! Ну, это ж как в коммуналке жить, только на уровне кода. Всем по отдельной комнатке, но если один сосед в сортире застрял — всем пиздец, света нет. Вот чтобы такого не было, умные дядьки придумали паттерны, чтобы не охуевать каждый раз.

Смотри, какие штуки есть, чтобы этот цирк с конями хоть как-то работал:

  1. API Gateway (Швейцар-хуевцар): Это такой главный хаб для всех, кто с улицы лезет. Все запросы — к нему. А он уже решает: тебе к Петровичу в сервис-аутентификацию, а тебя — к Машке в каталог товаров. Ещё и проверяет, не пьяный ли ты, и считает, сколько раз заходил. Удобно, блядь.

  2. Service Discovery (Где блядь Петрович?): Сервисы-то плодятся как тараканы, их адреса меняются. Этот паттерн — такая общая тетрадка, где все пишут: «Я, Петрович, сейчас живу вот тут». И когда Машке надо Петровича найти, она не по всей общаге бегает, а в эту тетрадку смотрит. Consul, etcd — это такие ответственные за тетрадь.

  3. Circuit Breaker (Автомат-ебанут): Самое важное, ёпта! Представь, сервис «Оплата» лег и не встает. А ты ему всё новые запросы суёшь. Он их тупо жрёт и сдыхает. Так вот этот предохранитель — он смотрит: если три раза подряд хуйня, он говорит «ВСЁ, БЛЯДЬ!» и разрывает цепь. Перестаёт слать запросы, даёт сервису отдышаться. А клиентам сразу говорит: «Сорян, не работает, иди нахуй». Потом, через время, осторожно пробует снова. Гениально и просто, как топор.

  4. Database per Service (Свой сортир — своя крепость): Это святое! Каждый сервис сосёт со своей собственной базы. Не лезь в мой горшок, я не лезу в твой. Иначе один накосячит со схемой, а все остальные срут кирпичами. Слабая связанность, ёба!

  5. Saga Pattern (Длинная и печальная сага): А как, сука, деньги списать с одного счёта и зачислить на другой, если это два разных сервиса? Одна большая транзакция не прокатит. Вот и делают цепочку: списали в одном сервисе — ок, зачислили в другом — ок. Если на втором шаге пиздец, то запускают обратный шаг (компенсирующую транзакцию), чтобы откатить первый. Как в кино: одно ебаное приключение за другим.

  6. CQRS (Разделяй и властвуй): Читать и писать — это две большие разницы, как говорят в Одессе. Вот и разделили: один сервис (или даже модель) только команды принимает — «создай», «измени», «удали». А другой — только отдаёт данные для чтения. Их можно независимо масштабировать. Тот, что читает, может хоть из пяти кэшей данные собирать, главное — быстро. А тот, что пишет, пусть себе в одной транзакции ковыряется.

Вот, смотри, как этот автомат-ебанут (Circuit Breaker) на Go выглядит, чтоб ты понимал, что это не магия, а код:

import (
    "time"
    "github.com/sony/gobreaker"
)

var cb *gobreaker.CircuitBreaker

func init() {
    settings := gobreaker.Settings{
        Name:        "my-external-service",
        MaxRequests: 1,
        Timeout:     5 * time.Second,
        ReadyToTrip: func(counts gobreaker.Counts) bool {
            return counts.ConsecutiveFailures > 3
        },
    }
    cb = gobreaker.NewCircuitBreaker(settings)
}

func CallMyService() (interface{}, error) {
    body, err := cb.Execute(func() (interface{}, error) {
        // Логика вызова внешнего сервиса
        return externalAPI.GetData()
    })
    if err != nil {
        return nil, err
    }
    return body, nil
}

Видишь? Настроили: если три раза подряд косяк (ConsecutiveFailures > 3) — всё, автомат вырубается, состояние Open. Потом стоит 5 секунд (Timeout), пробует снова. В общем, чтобы не охуевать от каскадных сбоев, когда один еблан-сервис тянет за собой всех.