В чем разница между Kafka и RabbitMQ

Ответ

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

RabbitMQ (Классический брокер сообщений / Message Broker)

  • Модель: Использует модель очередей и обменников (exchanges). Сообщения отправляются в обменник, который затем маршрутизирует их в одну или несколько очередей на основе правил маршрутизации (routing keys, headers).
  • Доставка сообщений: Ориентирован на надежную доставку сообщений. Поддерживает различные режимы подтверждения (acknowledgements) — ack/nack — для гарантии того, что сообщение было обработано потребителем.
  • Потребление: Сообщения удаляются из очереди после успешной обработки потребителем. Это означает, что одно сообщение обычно потребляется только одним потребителем (в случае конкурирующих потребителей на одной очереди).
  • Сценарии использования:
    • RPC (Remote Procedure Call): Задачи, требующие немедленного ответа.
    • Распределение задач: Отправка задач воркерам с гарантированной доставкой.
    • Сложная маршрутизация: Когда сообщения должны быть доставлены в разные очереди на основе сложных правил.
    • Короткоживущие сообщения: Сообщения, которые не требуют длительного хранения.
  • Масштабируемость: Масштабируется горизонтально, но может быть сложнее в управлении при очень высоких нагрузках и большом количестве очередей.

Kafka (Распределенный потоковый журнал / Distributed Streaming Platform)

  • Модель: Использует модель распределенного лога (log-oriented). Сообщения (записи) добавляются в конец лога (топика/партиции) и сохраняются в течение заданного времени (или до достижения определенного размера). Потребители читают сообщения из лога, отслеживая свой собственный офсет (позицию).
  • Доставка сообщений: Гарантирует порядок сообщений внутри одной партиции. Сообщения не удаляются после прочтения; они остаются доступными для других потребителей или для повторного чтения.
  • Потребление: Множество потребителей могут читать одни и те же сообщения из топика независимо друг от друга, каждый со своим офсетом. Это позволяет создавать множество независимых потоков обработки данных.
  • Сценарии использования:
    • Сбор логов и метрик: Высокопроизводительный сбор и агрегация данных.
    • Потоковая обработка данных: Анализ данных в реальном времени (например, с помощью Kafka Streams, Flink, Spark Streaming).
    • Event Sourcing: Хранение всех изменений состояния системы как последовательности событий.
    • Репликация данных: Надежная передача данных между системами.
  • Масштабируемость: Разработана для горизонтального масштабирования и обработки очень больших объемов данных с высокой пропускной способностью.

Примеры использования в Go:

RabbitMQ (с библиотекой streadway/amqp):

package main

import (
    "log"
    "github.com/streadway/amqp"
)

func main() {
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    if err != nil {
        log.Fatalf("Не удалось подключиться к RabbitMQ: %v", err)
    }
    defer conn.Close()

    ch, err := conn.Channel()
    if err != nil {
        log.Fatalf("Не удалось открыть канал: %v", err)
    }
    defer ch.Close()

    q, err := ch.QueueDeclare(
        "hello", // name
        false,   // durable
        false,   // delete when unused
        false,   // exclusive
        false,   // no-wait
        nil,     // arguments
    )
    if err != nil {
        log.Fatalf("Не удалось объявить очередь: %v", err)
    }

    msgs, err := ch.Consume(
        q.Name, // queue
        "",     // consumer
        true,   // auto-ack
        false,  // exclusive
        false,  // no-local
        false,  // no-wait
        nil,    // args
    )
    if err != nil {
        log.Fatalf("Не удалось зарегистрировать потребителя: %v", err)
    }

    forever := make(chan bool)

    go func() {
        for d := range msgs {
            log.Printf("Получено сообщение: %s", d.Body)
        }
    }()

    log.Printf(" [*] Ожидание сообщений. Для выхода нажмите CTRL+C")
    <-forever
}

Kafka (с библиотекой segmentio/kafka-go):

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/segmentio/kafka-go"
)

func main() {
    // Создаем Kafka Reader
    r := kafka.NewReader(kafka.ReaderConfig{
        Brokers:  []string{"localhost:9092"}, // Адреса Kafka брокеров
        Topic:    "my-topic",                 // Топик для чтения
        GroupID:  "my-consumer-group",        // Группа потребителей
        MinBytes: 10e3,                       // 10KB
        MaxBytes: 10e6,                       // 10MB
        MaxWait:  1 * time.Second,            // Максимальное время ожидания новых сообщений
    })

    ctx := context.Background()

    fmt.Println("Начало чтения сообщений из Kafka...")
    for {
        // Читаем следующее сообщение
        m, err := r.ReadMessage(ctx)
        if err != nil {
            log.Printf("Ошибка чтения сообщения: %v", err)
            break
        }
        fmt.Printf("Сообщение из топика %s, партиции %d, офсет %d: %s = %sn",
            m.Topic, m.Partition, m.Offset, string(m.Key), string(m.Value))
    }

    // Закрываем Reader
    if err := r.Close(); err != nil {
        log.Fatal("Не удалось закрыть reader: ", err)
    }
}

Вывод:

Выбор между Kafka и RabbitMQ зависит от конкретных требований вашего проекта:

  • Используйте RabbitMQ, если вам нужна надежная доставка отдельных сообщений, сложная маршрутизация, RPC-подобные взаимодействия или если сообщения не требуют длительного хранения.
  • Используйте Kafka, если вам нужна высокая пропускная способность, потоковая обработка больших объемов данных, возможность многократного чтения одних и тех же данных или построение систем на основе событий (event-driven architectures).

Ответ 18+ 🔞

Давай разберемся, что тут у нас за звери такие — Kafka и RabbitMQ. Это ж как два разных вида транспорта: один — надежный курьер с квитанцией в зубах, а второй — ебаный скоростной поезд с вагонами-хранилищами. И каждый хорош для своего дела, но если перепутать — будет пиздец, а не система.

RabbitMQ (Этот ваш классический брокер, как почтальон Печкин)

  • Как работает: Тут есть обменники (exchanges) и очереди. Кидаешь сообщение в обменник, а он уже, хитрая жопа, раскидывает его по нужным очередям по своим правилам. Как на почте сортировка.
  • Доставка: Всё с подтверждениями, прям как заказное письмо. Потребитель сказал «ack» — значит, получил, обработал, можно удалять. Сказал «nack» — что-то пошло не так, попробуй ещё раз, сука.
  • Кто читает: Сообщение из очереди выхватывает обычно один потребитель (ну или несколько конкурирующих, но всё равно — прочитали и удалили). Второй раз не прочтёшь, как прошлогоднюю газету.
  • Где годится:
    • RPC (Удалённый вызов): Когда нужно отправить запрос и прям щас ждать ответ, как будто в окошко кассы стучишь.
    • Распределение задач: Раздал задания воркерам — и спи спокойно, они доложат об успехе или провале.
    • Сложная маршрутизация: Если нужно одно сообщение размножить и послать в три разных места, да ещё с условиями.
    • Сообщения-однодневки: Которые пожили-пожили и сдохли, долго хранить их не надо.
  • Масштабирование: Можно и вширь растить, но когда очередей овердохуища — можно и головой об стенку биться от настройки.

Kafka (А это уже не брокер, а ёбаный распределённый журнал, как архив с бесконечной лентой)

  • Как работает: Представь огромный лог, дневник, куда все события пишутся строго по порядку в конец. Это топик. А чтобы быстрее было, его ещё на партиции делят. Сообщения не удаляются, а лежат себе заданное время. Хоть сто лет.
  • Доставка: Порядок внутри одной партиции гарантирован железно. Прочитал, запомнил место (офсет) — и хоть завтра с этого же места продолжи. Сообщения никуда не деваются.
  • Кто читает: Да хоть сто потребителей могут независимо ползать по этому логу и читать одни и те же данные. Каждый со своей скоростью и со своей позиции. Красота же!
  • Где годится:
    • Сбор логов и метрик: Тысячи сервисов плюют в него данными, а он не моргнув глазом всё проглатывает.
    • Потоковая обработка: Реальная аналитика, когда данные текут рекой и их надо фильтровать, агрегировать, обогащать на лету.
    • Event Sourcing: Когда состояние системы — это не просто «последняя версия», а вся история изменений, как в бухгалтерской книге, которую не подделаешь.
    • Репликация данных: Надёжно перекачать терабайты данных из точки А в точку Б.
  • Масштабирование: Рождён для этого. Хочешь больше пропускной? Добавь брокеров. Хочешь больше параллелизма? Увеличь число партиций. Просто и гениально, как топор.

Код на Go, чтобы не быть голословным:

RabbitMQ (тут streadway/amqp рулит):

package main

import (
    "log"
    "github.com/streadway/amqp"
)

func main() {
    // Пытаемся достучаться до кролика
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    if err != nil {
        log.Fatalf("Не удалось подключиться к RabbitMQ: %v", err) // Соединения нет — всё, пиздец
    }
    defer conn.Close()

    ch, err := conn.Channel()
    if err != nil {
        log.Fatalf("Не удалось открыть канал: %v", err)
    }
    defer ch.Close()

    // Объявляем очередь, если её ещё нет
    q, err := ch.QueueDeclare(
        "hello", // имя
        false,   // durable — переживёт ли рестарт брокера? Нет.
        false,   // delete when unused — удалить, если никто не пользуется
        false,   // exclusive — только для этого соединения
        false,   // no-wait — не ждать ответа от брокера
        nil,     // arguments — дополнительные аргументы, но нам похуй
    )
    if err != nil {
        log.Fatalf("Не удалось объявить очередь: %v", err)
    }

    // Начинаем слушать очередь, как подслушивающее устройство
    msgs, err := ch.Consume(
        q.Name, // queue
        "",     // consumer tag — можно свой, а можно и пустой
        true,   // auto-ack — автоматом подтверждать доставку? Да, пусть.
        false,  // exclusive
        false,  // no-local
        false,  // no-wait
        nil,    // args
    )
    if err != nil {
        log.Fatalf("Не удалось зарегистрировать потребителя: %v", err)
    }

    forever := make(chan bool)

    go func() {
        for d := range msgs { // Бесконечно вытаскиваем сообщения из канала
            log.Printf("Получено сообщение: %s", d.Body)
        }
    }()

    log.Printf(" [*] Ожидание сообщений. Для выхода нажмите CTRL+C")
    <-forever // Висим тут, пока нас не убьют
}

Kafka (берём segmentio/kafka-go):

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/segmentio/kafka-go"
)

func main() {
    // Читатель (Reader) — это наш потребитель
    r := kafka.NewReader(kafka.ReaderConfig{
        Brokers:  []string{"localhost:9092"}, // Адреса брокеров, их может быть дохуя
        Topic:    "my-topic",                 // Из какого топика читаем
        GroupID:  "my-consumer-group",        // Группа потребителей — для управления офсетами
        MinBytes: 10e3,                       // Минимум 10KB данных ждём перед чтением (для эффективности)
        MaxBytes: 10e6,                       // Максимум 10MB за раз прочитаем
        MaxWait:  1 * time.Second,            // Не будем ждать дольше секунды, если данных мало
    })

    ctx := context.Background()

    fmt.Println("Начало чтения сообщений из Kafka...")
    for {
        // Читаем следующее сообщение. Оно не удаляется, мы просто двигаем свой офсет.
        m, err := r.ReadMessage(ctx)
        if err != nil {
            log.Printf("Ошибка чтения сообщения: %v", err)
            break
        }
        // Выводим всю подноготную: топик, партицию, офсет, ключ и значение
        fmt.Printf("Сообщение из топика %s, партиции %d, офсет %d: %s = %sn",
            m.Topic, m.Partition, m.Offset, string(m.Key), string(m.Value))
    }

    // Закрываем Reader, как хорошие мальчики
    if err := r.Close(); err != nil {
        log.Fatal("Не удалось закрыть reader: ", err)
    }
}

Итог, ёпта:

Выбирать надо не по тому, что модно, а по тому, что твоей системе реально нужно.

  • Тащи RabbitMQ, если тебе нужны гарантированная доставка, сложные маршруты, RPC или задачи типа «сделай-и-забудь». Он как исполнительный курьер.
  • Хватай Kafka, если у тебя поток данных, как из пожарного шланга, и его нужно не просто доставить, а сохранить, перечитать, проанализировать и раздать двадцати потребителям. Это архив, конвейер и магистраль в одном флаконе.

Понял? А то некоторые берут Kafka для двух сообщений в день — это как на космическом корабле до соседнего подъезда ехать, волнение ебать.