Модуль 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 на каждый партицию-группу).

bash
docker 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 (в типовых бенчмарках)

  1. Нет JVM: нет GC-пауз, ниже память на тот же throughput.
  2. Thread-per-core без локов на hot path: линейное масштабирование по ядрам.
  3. 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 (очередь)

go
import (
	"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

go
import "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. Бизнес-код пишется против абстракции, инфраструктура — конфигурация.

go
import (
	"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. Неправильный — когда нужен каждый бит производительности или специфичной семантики брокера.

💻 Практика

  1. lesson5_1: запустите Redpanda, прогоните лабораторную 3.9 без правок кода — зафиксируйте throughput против Kafka в той же песочнице.
  2. lesson5_3: SQS+FIFO с двумя группами (LocalStack отдельно — docker run -p 4566:4566 localstack/localstack:3.8.1 — или реальный AWS free tier): покажите, что порядок соблюдается в группе и не соблюдается между ними.
  3. lesson5_4: один хендлер Watermill на двух драйверах (GoChannel и AMQP) — сравните тесты in-memory против docker-интеграционных.

🧩 Quiz "Проверь себя"

1. Pulsar key_shared subscription даёт...
2. SQS FIFO: какой throughput-лимит и что его поднимает?
3. Pulsar shared subscription vs Kafka consumer group: в чём фундаментальная разница балансировки?
4. SQS visibility timeout: консьюмер получил сообщение, обработал 40 секунд, таймаут 30. Что произойдёт?
5. Главная ценность Watermill для Go-сервиса?
6. Зачем нужен tiered storage (Redpanda/Kafka)?
7. В чём главная архитектурная идея Pulsar по сравнению с Kafka?
8. За счёт чего Redpanda даёт Kafka-совместимость без JVM?
9. GCP Pub/Sub: что нужно для гарантии порядка по ключу?
10. Какие утверждения про SQS/SNS и самохостинг брокеров верны? — выберите все подходящие

✅ Итоги модуля

После прохождения ты должен уметь:

  • Объяснить, что даёт 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 фреймворк-абстракция.

Шпаргалка модуля

ПараметрRedpandaPulsarSQS/SNSGCP Pub/SubWatermill
МодельKafka-логлог + мультитенанточередь (+fanout)очередь+стримабстракция
Порядокper-partition4 режима подписокFIFO-группы (лимиты)ordering keysнаследует драйвер
Эксплуатациясвой бинарьbroker+BookKeeperнет (облако)нет (облако)—
ThroughputвысокийвысокийFIFO: ограниченвысокийкак у драйвера
КейсKafka без JVMмультитенант, геобез opsбез ops + pushпереносимость

🔗 Мостик к Модулю 4

Экскурсия закончена — дальше конкретика. Модуль 4: NATS + JetStream — брокер, где Core-шина на миллионы сообщений в секунду живёт рядом с персистентными стримами, KV и Object Store, а клиентская библиотека умещается в один экран. Сравним его честно и с RabbitMQ, и с Kafka — цифрами и семантиками. Это последний модуль ядра: дальше — курсы по отдельным брокерам и капстоун.