Как устроены очереди и события?
Что проверяют
Различаете ли вы очередь задач и поток событий и умеете ли связать это с кодом на Go: от буферизированного канала внутри процесса до брокера между сервисами. Следом почти всегда спрашивают, что будет, если консьюмер упал посреди обработки, и как не обработать сообщение дважды.
Как отвечать
Разведите два уровня. Внутри процесса очередь это буферизированный канал и пул воркеров: продюсер пишет в chan Job, N горутин читают через range, размер буфера задаёт backpressure, остановка идёт через context.Context и close со стороны продюсера. Такая очередь живёт в памяти и теряется при рестарте.
Между сервисами нужен брокер, и моделей тут три:
- очередь (RabbitMQ): сообщение получает один потребитель, после
ackоно удаляется. Если соединение оборвалось до подтверждения, брокер вернёт сообщение в очередь; - лог событий (Kafka): топик разбит на партиции, сообщения хранятся по времени или объёму, потребитель лишь двигает свой offset. Разные consumer group читают один топик независимо, порядок гарантирован только внутри партиции, поэтому события одной сущности отправляют с одним ключом;
- pub/sub (NATS, Redis Pub/Sub): рассылка всем подписчикам без хранения, если не включать персистентность вроде JetStream.
На практике почти везде доставка at-least-once, значит обработчик должен быть идемпотентным: ключ дедупликации, INSERT ... ON CONFLICT DO NOTHING. Чтобы событие не потерялось между записью в базу и публикацией, используют transactional outbox: событие пишется в таблицу той же транзакцией, отдельный процесс отправляет его в брокер. Сообщения, которые стабильно падают, уходят в dead letter queue.
Из Go-библиотек уместно назвать segmentio/kafka-go или IBM/sarama, для RabbitMQ rabbitmq/amqp091-go.
Где спотыкаются
Отвечают только про каналы. Канал это очередь внутри одного процесса, он не переживает рестарт и не делится между инстансами.
Коммитят offset или делают ack до обработки. Упавший воркер тогда теряет сообщение без следа.
Обещают exactly-once «из коробки». Транзакции Kafka дают его только в пределах самой Kafka, а запись во внешнюю базу всё равно требует идемпотентности.