> Какой механизм используется для отправки событий? (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: InDrive

Стек: Go

> Пример ответа

Короткий ответ

В Go для отправки событий чаще всего используется механизм каналов (channels) в сочетании с горутинами. Каналы обеспечивают синхронизацию и передачу данных между конкурентными компонентами. Для более сложных сценариев применяются паттерны на основе каналов: fan-out, pub/sub, event bus. Альтернатива - использование стандартной библиотеки context для отмены и таймаутов, а также сторонних библиотек вроде watermill или go-eventbus, если требуется гарантированная доставка или маршрутизация.

Подробное объяснение

Каналы в Go - это типизированный механизм передачи данных между горутинами. Они бывают буферизированными и небуферизированными. Небуферизированный канал блокирует отправителя до получения данных получателем, что обеспечивает синхронизацию. Буферизированный позволяет отправителю продолжать работу, пока буфер не заполнен.

Для отправки события используется оператор ch <- event. Получение - event := <-ch. Каналы можно закрывать через close(ch), что сигнализирует получателям о завершении потока.

Для pub/sub паттерна создают структуру с map подписчиков и мьютексом для защиты. Каждый подписчик - отдельный канал. При отправке события оно копируется во все каналы подписчиков. Важно обрабатывать медленных подписчиков - либо использовать буферизированные каналы, либо горутины-посредники.

Также используется паттерн "worker pool": несколько горутин читают из одного канала и обрабатывают события конкурентно. Это даёт масштабирование и балансировку нагрузки.

На практике

При проектировании event-системы на Go важно решить: нужна ли гарантия доставки, порядок событий, обработка ошибок. Для простых случаев достаточно канала с буфером. Для распределённых систем - Kafka или NATS, но это уже не механизм языка, а внешняя инфраструктура.

Типичный подход - создать интерфейс EventBus с методами Publish(topic string, event interface{}) и Subscribe(topic string) <-chan interface{}. Внутри - map[string][]chan interface{} и sync.RWMutex. При публикации событие отправляется во все каналы подписчиков топика.

Важно помнить: отправка в закрытый канал вызывает panic. Поэтому нужна защита через мьютекс или проверку состояния. Также стоит избегать блокировки отправителя - использовать select с default или буферизированные каналы.

Пример кода

GO
type EventBus struct {
mu sync.RWMutex
subs map[string][]chan interface{}
}
func NewEventBus() *EventBus {
return &EventBus{subs: make(map[string][]chan interface{})}
}
func (b *EventBus) Subscribe(topic string) <-chan interface{} {
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan interface{}, 10)
b.subs[topic] = append(b.subs[topic], ch)
return ch
}
func (b *EventBus) Publish(topic string, event interface{}) {
b.mu.RLock()
defer b.mu.RUnlock()
for _, ch := range b.subs[topic] {
select {
case ch <- event:
default:
// подписчик медленный - пропускаем или логируем
}
}
}
// использование
bus := NewEventBus()
ch := bus.Subscribe("user.created")
go func() {
for ev := range ch {
fmt.Println(ev)
}
}()
bus.Publish("user.created", User{ID: 1})

Как отвечать на собеседовании

Начни с каналов как базового механизма. Затем покажи, как строится паттерн pub/sub. Упомяни trade-off: буферизация vs блокировка, гарантии доставки. Если спросят про распределённые системы - скажи, что для этого используют Kafka/NATS, а каналы - только в рамках одного процесса.

Приведи пример кода, но не слишком длинный. Покажи, что понимаешь проблемы: закрытие каналов, медленные подписчики, конкурентный доступ к map. Упомяни альтернативы: sync.Cond, atomic для простых сигналов, но каналы - идиоматичный способ.

Что проверяет интервьюер

Интервьюер оценивает знание конкурентных примитивов Go, понимание блокировок и синхронизации. Важно показать, что ты знаешь не только синтаксис, но и паттерны: fan-out, worker pool, pub/sub. Также проверяется умение проектировать API для событийной системы и обрабатывать edge cases.

Типичные ошибки

  • Отправка в закрытый канал - panic. Нужна защита.
  • Гонки при доступе к map подписчиков без мьютекса.
  • Бесконечная блокировка отправителя при медленном получателе - нужен select с default или буфер.
  • Неправильное использование буферизации: слишком большой буфер маскирует проблемы, слишком маленький - блокирует.
  • Забывают закрывать каналы подписчиков при отписке - утечка горутин.
  • Путают каналы с очередями: каналы не хранят события, если нет получателя - событие теряется.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью