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

Метрики
Чтобы наблюдать за количеством сокращений и переходов, нам нужны метрики. Конечно, мы можем подключить их в "ядре", и запариваться не придется, но мне хотелось прочувствовать микросервисы. И я выделил отдельный сервис - сервис статистик. Окей, Prometheus
есть, сервис есть, но теперь нам надо как-то подружить его с ядром. Идеальный выбор - Kafka
. Ядро асинхронно публикует события, а сервис статистик забирает их, когда хочет. Такой подход обеспечивает слабую связность: ядро не знает о статистике, а статистика не знает о ядре.
Кэширование
Мы же говорим о высоких нагрузках? Нам необходим кэш. Я выбрал Valkey
(форк Redis
) для этих целей.
Как кэшировать?
Так как у нас теперь есть отдельный сервис для всех аналитических данных, давайте подключим к нему ClickHouse
и будем писать все события в него.
Отлично! Теперь мы можем периодически собирать топ популярных ссылок по переходам, исходя из тех самых событий.
Как мы этот топ доставим до кэша? Все просто, снова воспользуемся кафкой. Ядро заберет его и запишет к себе в Valkey
.
Проблема масштабирования сервиса статистик
Среди этой схемы есть маааленькая проблемка: если у нас несколько инстансов статистик, каждый из них отправит топ в определенный момент. Мы явно не хотим получать кучу одинаковых сообщений в кафку. Это просто засоряет канал.
Как чинить?
Выход есть — распределенный лок!
Подключим к сервису статистик свой Valkey
, в который он будет писать одно значение с ттл при сборе топа. Таким образом, первый успевший захватить лок сможет собрать и отправить топ, а остальные получат отказ, потому что значение в Valkey
уже существует.
Трассировка
У нас уже 4 сервиса, и нам надо как-то следить за запросом по всем четырём. Смотреть логи каждого слишком нудно и долго. На помощь приходит трассировка с OpenTelemetry
. Эта вещь позволяет "трассировать" один запрос через все сервисы, не теряя контекст. Даже в кафку мы можем пропихнуть trace id в заголовках сообщения и тогда не потеряем трейс при прохождении через брокер. Но сам по себе OpenTelemetry
не поставляет никакого UI, он лишь является бэкендом. В качестве веб-морды можно использовать Jaeger
. Он предоставляет удобный интерфейс для просмотра трейсов.

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