Модуль 3. Современные альтернативы и облако
Цель модуля: получить рабочее представление о «втором эшелоне»: Redpanda (Kafka-API без JVM), Apache Pulsar (разделение compute/storage), облачные очереди (SQS+SNS, GCP Pub/Sub) и Watermill — фреймворк-абстракцию над брокерами. После модуля вы сможете обоснованно говорить «почему не Kafka» и «когда облако выгоднее самохостинга».
Перед началом: Модуль 4 этого курса (NATS) и курсы Kafka/RabbitMQ — семантики big three. Инструменты: docker-compose, AWS/GCP SDK (по желанию), Watermill. Трудоёмкость: ~8–10 ч (теория 3 ч · практика 5 ч · квиз 1 ч).
📖 3.1. Redpanda: Kafka API без JVM
Что это
Redpanda — брокер, говорящий на протоколе Kafka (клиенты не отличают), но написанный на C++ (фреймворк Seastar, thread-per-core, shared-nothing). Никаких JVM, ZooKeeper/KRaft-кворум встроен (Raft на каждый партицию-группу).
bashdocker run -d --name=redpanda -p 9092:9092 \ docker.redpanda.com/redpandadata/redpanda:latest \ redpanda start --overprovisioned --smp 1 \ --kafka-addr PLAINTEXT://0.0.0.0:9092 \ --advertise-kafka-addr PLAINTEXT://localhost:9092
Весь код из курса Kafka на segmentio/kafka-go работает без изменений —
поменяйте только адрес брокера. Это и есть главная фича: бинарная
совместимость с экосистемой при другой реализации.
За счёт чего быстрее/дешевле Kafka (в типовых бенчмарках)
- Нет JVM: нет GC-пауз, ниже память на тот же throughput.
- Thread-per-core без локов на hot path: линейное масштабирование по ядрам.
- Raft на партицию вместо контроллер-кворума — меньше движущихся частей.
Честные оговорки: в бенчмарках Redpanda часто выигрывает по p99 и стоимости на ядро, но экосистема и operational-опыт вокруг Apache Kafka глубже; kafka-go-клиент с Redpanda проверяйте на своих версиях протокола. Tiered storage (холодные сегменты в S3, локально — горячий хвост) есть у обоих, у Redpanda он исторически более «из коробки»: быстрее восстановление узла и дешевле держать длинный retention.
Когда выбирать: хотите Kafka-семантики и клиентскую экосистему, но устали от JVM-эксплуатации и стоимости кластера. Когда нет: нужны сотни интеграций и thousand-node экосистема Kafka Connect/Streams — там оригинал по-прежнему золото.
📖 3.2. Apache Pulsar: разделение вычислений и хранения
Главная идея
В Kafka вычисление и хранение склеены: брокер владеет сегментами своих партиций, и ребалансировка данных между узлами — дорогая операция. Pulsar разделяет роли:
text[Pulsar Broker] — stateless: принимает и раздаёт │ (пишет/читает) [Apache BookKeeper] — слой хранения: ансамбли буков (bookies), стрипинг
Следствия: добавление брокеров масштабирует чтение/запись мгновенно (stateless), хранение расширяется добавлением bookies, failover партиции — переподключение, а не перенос гигабайтов.
Топики и подписки
Иерархия: tenant/namespace/topic — мультитенантность и квоты на уровне
namespace из коробки. Подписки (аналог consumer groups) четырёх видов:
| Режим | Кто читает | Аналог |
|---|---|---|
Exclusive | один консьюмер | обычный reader |
Failover | активный + горячий запасной | Kafka-группа из 2 |
Shared | все консьюмеры делят сообщения | RabbitMQ competing consumers |
key_shared | консьюмер по хэшу ключа, порядок по ключу | Kafka по ключу |
Ключевой вопрос на сравнение: Pulsar Shared vs Kafka consumer group.
В Kafka партиция целиком назначается одному консьюмеру (балансировка —
партициями). В Pulsar Shared сообщения раздаются консьюмерам по одному
(балансировка — сообщениями): нет проблемы «3 партиции на 5 инстансов»,
но и нет гарантии порядка. Хотите порядок без привязки к партициям —
key_shared.
Когда выбирать Pulsar: мультитенантность, миллионы топиков, гео-репликация как требование, эластичное хранение. Цена: больше движущихся частей (broker + BookKeeper + ZK/KRaft-метаданные) и меньше Go-экосистема (официальный клиент есть, сообщество скромнее Kafka).
📖 3.3. Облачные очереди: SQS+SNS и GCP Pub/Sub
Зачем вообще облако
Нет кластера, нет мониторинга, нет апгрейдов — есть лимиты, цена за запрос и vendor lock-in. Для команд без infra-ресурса и нагрузок «до десятков тысяч rps» это часто рациональный выбор. Go SDK у обеих — первоклассный.
AWS: SNS (fanout) + SQS (очередь)
goimport ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/aws/aws-sdk-go-v2/service/sns" ) cfg, _ := config.LoadDefaultConfig(ctx) sqsClient := sqs.NewFromConfig(cfg) snsClient := sns.NewFromConfig(cfg) // fanout: топик SNS подписан на 3 очереди SQS _, err := snsClient.Publish(ctx, &sns.PublishInput{ TopicArn: &topicArn, Message: string(body), }) // очередь: long polling — обязательно, иначе поллинг съест бюджет out, err := sqsClient.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{ QueueUrl: &queueURL, MaxNumberOfMessages: 10, WaitTimeSeconds: 20, // long polling }) // после успешной обработки: _, _ = sqsClient.DeleteMessage(ctx, &sqs.DeleteMessageInput{ QueueUrl: &queueURL, ReceiptHandle: msg.ReceiptHandle, })
Что нужно держать в голове:
- SQS Standard: at-least-once, порядок не гарантирован, throughput практически неограничен.
- SQS FIFO: порядок и дедуп на
MessageGroupId/MessageDeduplicationId, но ценой лимитов: ~300 send/receive TPS на группу, до ~3000 с батчингом; high throughput mode поднимает до ~30k TPS (не во всех регионах). - Visibility timeout: получатель прячет сообщение на N секунд; не
вызвал
DeleteMessage— сообщение снова видно (дубль). Это ack-механизм RabbitMQ/JetStream, только с таймером и вручную. - DLQ: redrive policy на исходной очереди (maxReceiveCount) — те же идеи, что в 2.4, настройкой, а не кодом.
GCP Pub/Sub
goimport "cloud.google.com/go/pubsub" client, _ := pubsub.NewClient(ctx, projectID) topic := client.Topic("orders") topic.EnableMessageOrdering = true // требуется для ordering keys res := topic.Publish(ctx, &pubsub.Message{ Data: body, OrderingKey: accountID, // порядок в рамках ключа Attributes: map[string]string{"trace-id": traceID}, }) _, err := res.Get(ctx) // подтверждение публикации (аналог confirms)
Pull-подписка (как SQS) или push (HTTP-колбэк на ваш эндпоинт — Go-сервис просто обрабатывает POST). DLQ — dead letter topic с настройкой max delivery attempts. Ordering keys дают per-key порядок как в Kafka, но: при исчерпании retry-политики доставка ключа останавливается, пока сообщение не истечёт по deadline — ловушка, о которой нельзя забывать.
Выбор: SQS — простые очереди задач и fanout через SNS; GCP Pub/Sub — глобальность, ordering keys, push-приёмка. Оба — когда ops-команды нет; оба нетерпимы к «а дай-ка я перечитаю историю» — это именно очереди.
📖 3.4. Watermill: один интерфейс над всеми брокерами
Идея
Watermill (ThreeDotsLabs/watermill) даёт два интерфейса — Publisher и
Subscriber — и драйверы к RabbitMQ, Kafka, NATS, SQL, GoChannel, Google
Cloud Pub/Sub, AWS SQS/SNS. Бизнес-код пишется против абстракции,
инфраструктура — конфигурация.
goimport ( "github.com/ThreeDotsLabs/watermill" "github.com/ThreeDotsLabs/watermill-amqp/v2/pkg/amqp" "github.com/ThreeDotsLabs/watermill/message" "github.com/ThreeDotsLabs/watermill/message/router/middleware" ) func main() { logger := watermill.NewStdLogger(false, false) amqpConfig := amqp.DefaultDurableQueueConfig("amqp://guest:guest@localhost:5672/") subscriber, err := amqp.NewSubscriber(amqpConfig, logger) publisher, err := amqp.NewPublisher(amqpConfig, logger) router, err := message.NewRouter(message.RouterConfig{}, logger) // middleware — переиспользуемые политики на все хендлеры router.AddMiddleware( middleware.CorrelationID{Context: context.Background()}.Middleware, middleware.Retry{ MaxRetries: 3, Backoff: middleware.BackoffExponential{Initial: 100 * time.Millisecond}, }.Middleware, ) // брокер-независимый хендлер: topic in → topic out router.AddHandler( "payment", // имя хендлера "orders.events", // входной topic subscriber, "payments.commands", // выходной topic publisher, func(msg *message.Message) ([]*message.Message, error) { // бизнес-логика: работаем с message.Message, а не с amqp.Delivery return []*message.Message{message.NewMessage(watermill.NewUUID(), msg.Payload)}, nil }, ) _ = router.Run(context.Background()) }
Что это даёт (и чего не даёт)
| Дает | Не даёт |
|---|---|
| Переносимость: смена брокера = замена драйвера | Доступа к специфике: JetStream-ручки из 4.2 придётся оборачивать |
| Тестируемость: GoChannel in-memory драйвер в юнит-тестах | Производительности per-broker тюнинга «из коробки» |
| Готовые middleware: retry, poison queue, correlation, throttle | Модели лога Kafka: оффсеты/replay прячутся за абстракцией |
| CQRS-хелперы: command/event bus | — |
Watermill — правильный ответ, когда абстракция важнее контроля: MVP (капстоун трека использует его сознательно), микросервисы с разными брокерами, тестирование бизнес-логики in-memory. Неправильный — когда нужен каждый бит производительности или специфичной семантики брокера.
💻 Практика
lesson5_1: запустите Redpanda, прогоните лабораторную 3.9 без правок кода — зафиксируйте throughput против Kafka в той же песочнице.lesson5_3: SQS+FIFO с двумя группами (LocalStack отдельно —docker run -p 4566:4566 localstack/localstack:3.8.1— или реальный AWS free tier): покажите, что порядок соблюдается в группе и не соблюдается между ними.lesson5_4: один хендлер Watermill на двух драйверах (GoChannel и AMQP) — сравните тесты in-memory против docker-интеграционных.
🧩 Quiz "Проверь себя"
✅ Итоги модуля
После прохождения ты должен уметь:
- Объяснить, что даёт Redpanda (Kafka-протокол без JVM) и чем за это платишь (экосистема, зрелость).
- Описать архитектуру Pulsar (broker + BookKeeper) и назвать все четыре режима подписок с их аналогами в Kafka/RabbitMQ.
- Различать SQS Standard vs FIFO: гарантии, лимиты, visibility timeout, DLQ через redrive.
- Использовать GCP Pub/Sub с ordering keys и знать его ловушку остановки ключа на retry.
- Написать брокер-независимый хендлер на Watermill с retry- и poison-мiddleware и трезво оценить цену абстракции.
- Аргументированно выбрать: самохост vs облако vs фреймворк-абстракция.
Шпаргалка модуля
| Параметр | Redpanda | Pulsar | SQS/SNS | GCP Pub/Sub | Watermill |
|---|---|---|---|---|---|
| Модель | Kafka-лог | лог + мультитенант | очередь (+fanout) | очередь+стрим | абстракция |
| Порядок | per-partition | 4 режима подписок | FIFO-группы (лимиты) | ordering keys | наследует драйвер |
| Эксплуатация | свой бинарь | broker+BookKeeper | нет (облако) | нет (облако) | — |
| Throughput | высокий | высокий | FIFO: ограничен | высокий | как у драйвера |
| Кейс | Kafka без JVM | мультитенант, гео | без ops | без ops + push | переносимость |
🔗 Мостик к Модулю 4
Экскурсия закончена — дальше конкретика. Модуль 4: NATS + JetStream — брокер, где Core-шина на миллионы сообщений в секунду живёт рядом с персистентными стримами, KV и Object Store, а клиентская библиотека умещается в один экран. Сравним его честно и с RabbitMQ, и с Kafka — цифрами и семантиками. Это последний модуль ядра: дальше — курсы по отдельным брокерам и капстоун.