6. NATS vs Kafka vs RabbitMQ

Сравнительный архитектурный анализ

«Существует два способа создания архитектуры программного обеспечения: один — сделать ее настолько простой, чтобы в ней очевидно не было дефектов; другой — сделать ее настолько запутанной, чтобы в ней не было очевидных дефектов».
— С.Э.Р. Хоар


Архитектурные парадигмы: разные ответы на разные вызовы

Выбор между NATS, Kafka и RabbitMQ — это не выбор «какой инструмент лучше», а фундаментальное архитектурное решение, определяющее топологию сети, аппаратные затраты, сложность эксплуатации и гарантии доставки распределенной системы.

Каждая из систем создавалась для принципиально разного класса задач:

  1. NATS (Cloud-Native Nervous System): проектировался как сверхбыстрая, эфемерная и предельно простая «нервная система» для микросервисов и edge-устройств. Приоритет — микросекундная задержка, минимальный footprint и нулевая головная боль при эксплуатации.
  2. RabbitMQ (Smart Broker, Dumb Consumer): классический представитель enterprise-брокеров (AMQP). Брокер берет на себя всю тяжесть сложной маршрутизации, фильтрации по заголовкам, приоритизации и подтверждений каждого сообщения, делая код клиентов элементарным.
  3. Apache Kafka (Dumb Broker, Smart Consumer): распределенный масштабируемый commit-log. Брокер не следит за тем, кто и что прочитал; он просто сохраняет терабайты упорядоченных событий на диск с максимальной пропускной способностью (throughput) для аналитики и Data Lake.

Сравнительная таблица

Ниже приведено детальное сопоставление ключевых характеристик систем:

Характеристика NATS (Core) NATS (JetStream) RabbitMQ Kafka
Модель маршрутизации Subject-based pub/sub Subject-based streams Exchange/Queue/Binding Topic-based partitions
Хранение В памяти Постоянное/В памяти Постоянное Append-only log
Гарантии доставки At-most-once At-least-once, Exactly-once Настраиваемые At-least-once
Упорядочивание Без гарантий Упорядочивание в рамках партиции Упорядочивание в очереди Упорядочивание в рамках партиции
Производительность Очень высокая Высокая Средняя Высокая
Потребление ресурсов Минимальное Среднее Среднее+ Высокое
Сложность развёртывания Минимальная Низкая Средняя Высокая
Очереди Queue Groups Consumer Groups Встроенные Партиции
Поддержка стриминга Ограниченная Встроенная ❌ Отличная
Язык реализации Go Go Erlang Java/Scala

Подробное сравнение по брокерам

1. NATS (Core)

Когда использовать:

  • Обмен сообщениями в реальном времени (чат, live-обновления котировок, многопользовательские игры);
  • Service discovery и health checks сервисов;
  • Коммуникация с низкой задержкой (<1 мс RTT в локальной сети);
  • Трансляция событий (где потеря нескольких сообщений при сетевом сбое не критична).
Go
// NATS Core: Простой pub/sub
nc.Publish("live.prices", []byte("BTC: $43250"))
nc.Subscribe("live.prices", func(msg *nats.Msg) {
    updatePrice(string(msg.Data))
})

Преимущества:

  • Минимальное потребление ресурсов: ~50 МБ RAM, бинарник ~10 МБ;
  • Экстремально высокая скорость: задержка <1 мс, миллионы сообщений в секунду;
  • Простота развёртывания: один бинарный файл, минимальная конфигурация;
  • Лёгкие клиенты: Go-клиент ~1 МБ, только стандартная библиотека net.

Недостатки:

  • Отсутствие персистентности: потеря сообщений при перезапуске сервера;
  • Нет гарантий доставки: модель fire-and-forget;
  • Ограниченное упорядочивание: нет гарантий порядка сообщений.

2. NATS JetStream

Когда использовать:

  • Event sourcing и аудит-логи;
  • Реализация CQRS;
  • Очереди задач с retry-логикой и dead-letter стратегиями;
  • Стриминг данных с гарантиями доставки.
Go
// JetStream: Персистентные сообщения
js.Publish("transactions.log", []byte(transactionData))

// Consumer с сохранением состояния
js.Subscribe("transactions.log", func(msg *nats.Msg) {
    processTransaction(msg)
    msg.Ack() // Подтверждение обработки
}, nats.Durable("tx_processor"))

Преимущества:

  • Персистентность: сообщения сохраняются на диск;
  • Гарантии доставки: at-least-once, поддержка exactly-once через дедупликацию;
  • Replay сообщений: чтение с любого оффсета;
  • Consumer groups: встроенный load balancing;
  • Консистентность уровня RAID: репликация на основе RAFT.

Недостатки:

  • Более высокое потребление ресурсов: больше RAM/диска по сравнению с Core NATS;
  • Увеличенная сложность: больше опций конфигурации;
  • Кривая обучения: необходимо понимать концепции Stream/Consumer.

3. RabbitMQ

Когда использовать:

  • Enterprise-месседжинг со сложной маршрутизацией;
  • Транзакционная обработка сообщений;
  • Приоритизация сообщений и TTL;
  • Требования к совместимости с AMQP.
Go
// RabbitMQ: Сложная маршрутизация
channel.ExchangeDeclare("orders", "topic", true, false, false, false, nil)
channel.Publish("orders", "order.created", false, false, amqp.Publishing{
    Body: []byte(orderData),
})

Преимущества:

  • Гибкая маршрутизация: exchanges типов direct, fanout, topic, headers;
  • Подтверждения доставки: надёжные гарантии доставки сообщений;
  • Dead letter queues: встроенная обработка "отравленных" сообщений;
  • Приоритетные очереди: возможность приоритизации сообщений;
  • Веб-интерфейс управления: богатый UI для администрирования.

Недостатки:

  • Требователен к ресурсам: высокое потребление памяти и CPU;
  • Сложная установка: требует Erlang VM;
  • Более медленный: более высокая задержка по сравнению с NATS;
  • Операционная сложность: больше компонентов для управления.

4. Apache Kafka

Когда использовать:

  • Big data пайплайны и аналитика;
  • Агрегация логов в реальном времени;
  • Event streaming в масштабе;
  • Долгосрочное хранение событий.
Go
// Kafka: Партиционированный стриминг
producer.send(new ProducerRecord<>("orders", "partition-key", orderData))

consumer.subscribe(Collections.singletonList("orders"))
for {
    records := consumer.poll(Duration.ofMillis(100))
    for record := range records {
        processOrder(record.value())
    }
}

Преимущества:

  • Высокая пропускная способность: миллионы сообщений в секунду;
  • Горизонтальное масштабирование: простое партиционирование;
  • Стрим-процессинг: Kafka Streams, KSQL;
  • Долгосрочное хранение: настраиваемые политики хранения;
  • Exactly-once семантика: через идемпотентных продюсеров.

Недостатки:

  • Высокая операционная сложность: ZooKeeper (или современный KRaft), множество сервисов;
  • Требователен к ресурсам: высокое потребление диска и памяти;
  • Крутая кривая обучения: сложные концепции (партиции, оффсеты и т.д.);
  • Не подходит для request-reply: лучше только для pub/sub.

Сравнение производительности

Go
// NATS Core: ~1M сообщений/сек, задержка <1 мс
func benchmarkNATSCore() {
    // Очень быстрая маршрутизация в памяти
    nc.Publish("test.subject", []byte("data"))
}

// NATS JetStream: ~100K-500K сообщений/сек, задержка ~1-10 мс
func benchmarkNATSJetStream() {
    // Персистентность, но всё ещё очень быстро
    js.Publish("test.stream", []byte("data"))
}

// RabbitMQ: ~50K-100K сообщений/сек, задержка ~10-100 мс
func benchmarkRabbitMQ() {
    // Накладные расходы AMQP, персистентность
    channel.Publish("", "queue", false, false, amqp.Publishing{...})
}

// Kafka: ~1M+ сообщений/сек, задержка ~5-50 мс
func benchmarkKafka() {
    // Высокая пропускная способность, но большая задержка для мелких сообщений
    producer.send(record)
}
TEXT
    Латентность против Пропускной способности (Latency vs Throughput):

    NATS Core:     [ 0.05 ms ] ========> Low latency champion (межсервисный диалог)
    JetStream:     [ 1.5 ms  ] ========> Durable stream champion (микросервисы)
    RabbitMQ:      [ 8.0 ms  ] ========> Rich enterprise routing (очереди задач)
    Kafka:         [ 15.0 ms ] ===================================> Bulk Throughput (ТБ логов)

Архитектурные паттерны

1. Коммуникация микросервисов

graph TB
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    subgraph "Подход NATS"
        S1["Сервис A"]:::network -->|"NATS Core"| NS["NATS Server"]
        S2["Сервис B"] -->|"NATS Core"| NS
        S3["Сервис C"] -->|"NATS Core"| NS
    end

    subgraph "Подход Kafka"
        SA["Сервис A"]:::process -->|"Produce"| KS["Kafka Cluster"]
        SB["Сервис B"] -->|"Consume"| KS
        SC["Сервис C"] -->|"Consume"| KS
    end

    subgraph "Подход RabbitMQ"
        SAA["Сервис A"]:::process -->|"Publish"| RMQ["RabbitMQ"]
        SBB["Сервис B"] -->|"Consume"| RMQ
        SCC["Сервис C"] -->|"Consume"| RMQ
    end
  

2. Event Sourcing

Решение Подходит Причина
NATS JetStream ✅ Отлично Встроенные стримы, персистентность, replay
Kafka ✅ Отлично Создан для event logs, высокая пропускная способность
RabbitMQ ⚠️ Условно Возможно, но требует очередей на агрегат

3. Request-Reply

Решение Подходит Причина
NATS ✅ Отлично Встроенный паттерн request-reply
Kafka ❌ Плохо Не предназначен для синхронной коммуникации
RabbitMQ ✅ Хорошо Поддерживает паттерн RPC

Потребление ресурсов

Сравнение аппаратных аппетитов систем в типовом продакшене:

Bash
# Потребление памяти (типичные развёртывания)
NATS Core:        ~50 МБ на сервер
NATS JetStream:   ~100-500 МБ на сервер (зависит от хранилища)
RabbitMQ:         ~1-2 ГБ на сервер (накладные расходы Erlang)
Kafka:            ~2-8 ГБ на брокер (накладные расходы JVM)

# Потребление диска (для персистентных сценариев)
NATS JetStream:   Эффективные append-only логи
RabbitMQ:         Индексы сообщений, очереди на диске
Kafka:            Высокооптимизированные сегментные файлы

Операционная сложность

Аспект NATS RabbitMQ Kafka
Установка Один бинарник Сложная (Erlang) Множество сервисов
Конфигурация Минимальная Расширенная Расширенная
Мониторинг Простые метрики Богатый UI + метрики Сложные метрики
Масштабирование Простое Умеренное Сложное
Бэкап/Восстановление Простое копирование файлов Сложное Сложное

Рекомендации по выбору

Выбирайте NATS, когда:

  • Нужна минимальная задержка (<1 мс);
  • Service mesh или коммуникация между микросервисами;
  • Приложения реального времени (гейминг, чат, live-обновления);
  • Простое развёртывание и низкое потребление ресурсов;
  • Паттерны request-reply поверх асинхронного транспорта.

Выбирайте NATS JetStream, когда:

  • Нужна персистентность с простотой NATS;
  • Event sourcing или аудит-логи;
  • Стрим-процессинг в экосистеме NATS;
  • Гибридный подход (часть сообщений персистентна, часть — нет).

Выбирайте RabbitMQ, когда:

  • Сложная маршрутизация (множество exchanges);
  • Enterprise-фичи (приоритетные очереди, TTL, DLQ);
  • Требуется совместимость с AMQP;
  • Транзакционная обработка сообщений.

Выбирайте Kafka, когда:

  • Big data пайплайны и аналитика;
  • Высокая пропускная способность (миллионы сообщений/сек);
  • Долгосрочное хранение и replay событий;
  • Стрим-процессинг (Kafka Streams);
  • Архитектура с горизонтальным масштабированием.

Сценарии миграции

NATS → Kafka (при росте сложности)

Когда система вырастает из рамок межсервисного обмена и требует подключения Spark/Flink, аналитических озер данных и долгосрочного хранения терабайтов событий:

Go
// До: NATS простой pub/sub
nc.Publish("events", data)

// После: Kafka с партиционированием
producer.send(new ProducerRecord<>("events", partitionKey, data))

RabbitMQ → NATS (для повышения производительности)

Когда традиционный стек RabbitMQ начинает задыхаться по CPU/RAM при росте числа микросервисов до сотен инстансов, а сложная топология AMQP-обменников становится источником задержек:

Go
// До: RabbitMQ exchange binding
channel.Publish("exchange", "routing.key", false, false, msg)

// После: NATS subject routing
nc.Publish("routing.key", data)


Итог

  • NATS Core: Максимальная простота и производительность для сценариев реального времени.
  • NATS JetStream: Золотая середина между простотой NATS и гарантиями доставки уровня enterprise.
  • RabbitMQ: Enterprise-возможности и гибкая маршрутизация ценой операционной сложности.
  • Kafka: Масштабируемость и персистентность для big data и аналитики.

Выбор зависит от архитектурных требований: если нужна скорость и простота — NATS. Если масштаб и аналитика — Kafka. Если enterprise-фичи — RabbitMQ. Часто в одной зрелой архитектуре используется комбинация брокеров под разные задачи (например, NATS для межсервисной синхронизации и быстрых RPC, а Kafka — для аналитического Data Lake).

В заключительной лекции подраздела — 7. Когда выбирать NATS — мы сведем все полученные знания в наглядные решающие деревья и чек-листы для архитектора.