Вполне логично предположить, что сократитель ссылок — довольно простой сервис как с точки зрения пользователя, так и под капотом. Но что, если, взяв за основу такую простую задачу, построить целую распределенную систему?

Мой шортенер начинался как простая практика с Go и gRPC после всех ОГЭ :-), где должно было быть 3 простеньких сервиса: inline тг бот, API gateway и ядро. Но с каждым днем идей все больше, энтузиазм растёт, я стал делать упор на высокие нагрузки, и постепенно мини-практика начала становиться боевой event-driven машиной. В этой статье я хотел бы подметить интересную мысль: даже самая простая вещь может быть реализована сложно.

Архитектура всего проекта
Архитектура всего проекта

Метрики

Чтобы наблюдать за количеством сокращений и переходов, нам нужны метрики. Конечно, мы можем подключить их в "ядре", и запариваться не придется, но мне хотелось прочувствовать микросервисы. И я выделил отдельный сервис - сервис статистик. Окей, Prometheus есть, сервис есть, но теперь нам надо как-то подружить его с ядром. Идеальный выбор - Kafka. Ядро асинхронно публикует события, а сервис статистик забирает их, когда хочет. Такой подход обеспечивает слабую связность: ядро не знает о статистике, а статистика не знает о ядре.

Кэширование

Мы же говорим о высоких нагрузках? Нам необходим кэш. Я выбрал Valkey (форк Redis) для этих целей.

Как кэшировать?

Так как у нас теперь есть отдельный сервис для всех аналитических данных, давайте подключим к нему ClickHouse и будем писать все события в него.

Отлично! Теперь мы можем периодически собирать топ популярных ссылок по переходам, исходя из тех самых событий.

Как мы этот топ доставим до кэша? Все просто, снова воспользуемся кафкой. Ядро заберет его и запишет к себе в Valkey.

Проблема масштабирования сервиса статистик

Среди этой схемы есть маааленькая проблемка: если у нас несколько инстансов статистик, каждый из них отправит топ в определенный момент. Мы явно не хотим получать кучу одинаковых сообщений в кафку. Это просто засоряет канал.

Как чинить?

Выход есть — распределенный лок!

Подключим к сервису статистик свой Valkey, в который он будет писать одно значение с ттл при сборе топа. Таким образом, первый успевший захватить лок сможет собрать и отправить топ, а остальные получат отказ, потому что значение в Valkey уже существует.

Трассировка

У нас уже 4 сервиса, и нам надо как-то следить за запросом по всем четырём. Смотреть логи каждого слишком нудно и долго. На помощь приходит трассировка с OpenTelemetry. Эта вещь позволяет "трассировать" один запрос через все сервисы, не теряя контекст. Даже в кафку мы можем пропихнуть trace id в заголовках сообщения и тогда не потеряем трейс при прохождении через брокер. Но сам по себе OpenTelemetry не поставляет никакого UI, он лишь является бэкендом. В качестве веб-морды можно использовать Jaeger. Он предоставляет удобный интерфейс для просмотра трейсов.

Jaeger UI
Jaeger UI

И я уже вижу вопрос «зачем весь этот оверинжиниринг для сокращения ссылок?»:‑). Моя тактика заключалась в том, чтобы не заостряться на бизнес‑логике ради изучения инфраструктуры. Это позволило мне эффективно потрогать множество продакшн технологий.

Вся система от начала и до конца написана на Go. Это был невероятно увлекательный путь, но вполне возможно, что он еще не прекращается! Реализацию можно глянуть на GitHub.

Буду рад вашим комментариям!

Комментарии (0)