Elido
9 мин чтенияИнженерия

Как работают сокращатели URL? Объяснение механики

Как работают сокращатели URL? Хранят соответствие слага и адреса назначения, ищут ключ при каждом клике и возвращают HTTP-редирект. Механика от начала до конца

Marius Voß
DevRel · edge infra
Конвейер от запроса к поиску слага до редиректа 302, ведущего к целевому URL, в цветовой палитре Elido

Откройте elido.me/abc123, и что-то должно превратить эту короткую строку в полный веб-адрес, прежде чем браузер сможет что-либо загрузить. Механизм проще, чем большинство предполагает. Сокращатель URL хранит соответствие между коротким кодом и длинным целевым URL. Когда вы нажимаете на короткую ссылку, сервис использует код как ключ поиска, находит адрес назначения и возвращает HTTP-редирект, указывающий браузеру, куда на самом деле идти. Один запрос входит, один редирект выходит.

Вот и весь принцип. Всё остальное - инженерные решения вокруг трёх задач: сделать поиск быстрым, обеспечить краткость и уникальность кодов, и записать клик без замедления процесса. В этой статье мы рассмотрим, как работают сокращатели URL от начала до конца, используя пограничную архитектуру Elido как конкретный пример, при этом сохраняя объяснение применимым к сокращателям в целом. Мы разберём соответствие слага и URL, способы генерации коротких кодов, место хранения данных, выбор между редиректами 301 и 302, то, как выглядит HTTP-редирект на уровне сети, почему важно кэширование на пограничных серверах, и как клик считается асинхронно.

Как работают сокращатели URL: соответствие в центре#

Уберите инфраструктуру, и сокращатель URL окажется хранилищем ключ-значение с обработчиком редиректа. Ключ - это слаг, короткий код после домена. Значение - адрес назначения, длинный URL, который вы изначально вставили.

Когда вы создаёте короткую ссылку, сокращатель записывает одну строку: этот слаг указывает на такой-то адрес назначения. Когда кто-то посещает короткую ссылку, сокращатель читает эту строку и действует по ней. Создание ссылок - редкое событие; чтение - постоянное. Одна маркетинговая ссылка может быть создана один раз, а затем прочитана несколько сотен тысяч раз за свою жизнь. Это соотношение «много чтений, мало записей» - самый важный факт о нагрузке, и он определяет каждое последующее проектное решение, особенно касательно кэширования.

Само соответствие может содержать больше, чем просто адрес назначения. В Elido слаг может содержать правила таргетинга, так что одна короткая ссылка направляет на разные адреса в зависимости от страны, устройства, языка или времени. Это то, что мы называем умной ссылкой, и это всё тот же поиск, просто с небольшой оценкой правил после чтения. Основное отношение не меняется: слаг на входе, адрес назначения на выходе.

Генерация короткого кода#

Если слаг - это ключ, откуда он берётся? Существует два проверенных подхода, и большинство сокращателей используют один или их сочетание.

Первый - кодирование ID базы данных в base62. Каждая новая ссылка получает автоинкрементный целочисленный ID из базы данных. Этот ID кодируется в base62, использующем 62 URL-безопасных символа a-z, A-Z и 0-9. ID 1 становится b, ID 125 превращается в двухсимвольный код, и так далее. Base62 плотный: три символа покрывают около 238 000 ссылок, пять символов - примерно 916 миллионов, шесть - около 56 миллиардов. Коды остаются короткими и, поскольку соответствуют уникальным ID один к одному, никогда не сталкиваются. Компромисс состоит в том, что последовательные ID предсказуемы, поэтому многие системы перемешивают или смещают пространство ID перед кодированием.

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

Пользовательские слаги - брендированные, вроде elido.me/spring-sale, - существуют поверх любой из схем. Пользователь задаёт строку; сокращатель проверяет её на допустимые символы и уникальность по тому же индексу перед сохранением. Независимо от того, сгенерирован слаг или выбран вручную, он оказывается в одном месте: в уникальном столбце хранилища данных.

Где хранятся данные#

Соответствие слага и адреса назначения нуждается в хранилище, которое может быстро и последовательно ответить на вопрос «на что указывает этот слаг». Для источника истины таким хранилищем почти всегда является реляционная база данных. Elido использует Postgres, где слаг хранится как уникальный индексированный столбец, так что поиск - это единственное чтение по ключу, а не сканирование таблицы. Postgres хранит каноническую запись для каждой ссылки, пользователя и рабочего пространства.

Но обращаться к Postgres при каждом отдельном клике было бы расточительно, учитывая соотношение «много чтений». Поиск слага по ключу в Postgres обычно занимает от одной до трёх миллисекунд, что звучит быстро - до тех пор, пока не умножишь это на объём кликов вирусной ссылки и не вспомнишь, что подключение к базе данных - ресурс конечный. Поэтому продакшн-сокращатели ставят кэш перед базой данных. Именно из кэша обслуживается большинство чтений; база данных - запасной вариант для того, чего в кэше ещё нет.

Схема потока: GET-запрос браузера для elido.me/abc123 проверяет внутрипроцессный L1 LRU-кэш, при промахе обращается к L2 Redis-кэшу, затем при полном промахе делает gRPC-вызов к api-core, и возвращает редирект 302 с адресом назначения в заголовке Location

Elido использует двухуровневый кэш перед Postgres. Первый уровень - внутрипроцессный LRU-кэш, находящийся внутри самого бинарника редиректа, который возвращает адрес назначения за несколько сотен наносекунд без сетевого перехода вообще. Второй уровень - кластер Redis в том же регионе, обслуживающий менее чем за миллисекунду. Только «холодный» промах - слаг, которого недавно не было ни на одном уровне, - проваливается до исходного gRPC-вызова к api-core, который читает Postgres. Совокупный коэффициент попаданий по обоим уровням кэша составляет около 99,4%, так что к базе данных обращается примерно один запрос из 167. Полное описание поведения кэша, включая политику вытеснения и режимы сбоев, - в нашем описании стратегии кэширования.

Что такое HTTP-редирект#

После того как сокращатель получил адрес назначения, ему нужно передать его браузеру. Это делается через HTTP-редирект - особый тип ответа. Вместо того чтобы возвращать содержимое страницы со статусом 200 OK, сервер возвращает код состояния 3xx и заголовок Location, указывающий реальный URL. На уровне сети ответ небольшой:

HTTP/1.1 302 Found
Location: https://shop.example.com/spring-collection
Content-Length: 0

Браузер читает код состояния, видит заголовок Location и немедленно делает новый запрос по этому адресу. Для человека, нажимающего на ссылку, это выглядит как одна навигация; под капотом это два запроса, где короткая ссылка выступает быстрым поиском в директории посередине. Семантика каждого кода 3xx определена в RFC 7231, а руководство Mozilla по HTTP-редиректам - наиболее понятный практический справочник о том, что делает каждый код.

Тело ответа пустое, потому что отображать нечего. Весь полезный груз - строка статуса и заголовок. Именно поэтому редиректы дёшевы в обслуживании: нет шаблона, нет JOIN по базе данных для контента, нет разметки. Разрешить слаг, установить один заголовок, отправить.

301 против 302: выбор, определяющий вашу аналитику#

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

301 Moved Permanently сообщает браузеру и каждому прокси и CDN между вами и сервером, что эта короткая ссылка всегда будет указывать на одно место. Поэтому они кэшируют её. 301 агрессивно сохраняется. Когда в следующий раз посетитель нажмёт на эту короткую ссылку, его браузер может разрешить её из кэша и никогда не обратиться к сокращателю. Это хорошо для экономии round-trip, и это катастрофа для инструмента ссылок, потому что ломаются две вещи. Во-первых, аналитика слепнет: клики, обслуживаемые из браузерного кэша, никогда не достигают вашего сервера, поэтому они никогда не считаются. Во-вторых, адрес назначения фактически заморожен. Если вы измените, куда указывает ссылка, все, кто уже закэшировал 301, продолжат попадать на старый адрес до истечения срока кэша - которым вы не управляете.

Сравнение рядом: постоянный редирект 301 (кэшируется браузером, аналитика слепнет, адрес назначения сложно изменить) против временного редиректа 302 (запрашивается каждый раз заново, аналитика работает, адрес назначения редактируем)

302 Found (и его более строгий аналог 307 Temporary Redirect) сообщает браузеру, что это временно: приходи и спрашивай снова в следующий раз. Браузер не кэширует соответствие, поэтому каждый клик повторно запрашивает короткую ссылку у сервера. Этот дополнительный round-trip - именно то, чего хочет инструмент для ссылок. Каждый клик достигает вашей инфраструктуры, поэтому каждый клик можно посчитать, и поскольку сервер каждый раз разрешает адрес назначения заново, вы можете изменить, куда указывает ссылка, и новый адрес вступит в силу при следующем клике. Цена - один сетевой round-trip на клик, который хорошо построенный пограничный сервер удерживает в пределах единиц миллисекунд.

Именно поэтому Elido по умолчанию использует 302. Редактируемые адреса назначения и точные данные о кликах - это весь смысл управляемой ссылки, а 301 жертвует обоим ради оптимизации кэша, которая обычно не нужна. RFC 7231 указывает, что 301 кэшируется по умолчанию, а 302 не сохраняется без специальных заголовков - именно то поведение, которое требуется в обоих случаях использования. Есть узкие случаи, где постоянный редирект правильен - например, настоящая миграция домена, - но для отслеживаемых, редактируемых коротких ссылок временный редирект является правильным значением по умолчанию.

Кэширование на пограничных серверах для низкой задержки#

Редирект синхронный и блокирующий. Браузер посетителя ждёт на короткой ссылке, пока не получит редирект, и только тогда может начать загружать действительно важную страницу. Каждая миллисекунда, затраченная на разрешение слага, добавляется к ожиданию посетителя. Именно поэтому серьёзные сокращатели переносят поиск как можно ближе к посетителю.

Elido запускает обработчик редиректа на пограничных точках присутствия во Франкфурте, Эшберне и Сингапуре, маршрутизируя трафик к ближайшей. Обработчик написан на Go поверх fasthttp, выбранного за то, что его путь обработки запросов без выделений памяти делает паузы сборщика мусора предсказуемыми под устойчивой нагрузкой. В сочетании с кэшем в памяти это удерживает редиректы на p95 менее 15мс при кэш-хите, измеренном на POP: примерно 4,8мс медиана во Франкфурте, до около 14мс p95 в Сингапуре, где география шире. Основная часть этого бюджета - физический транзит по сети, неизбежное расстояние между посетителем и ближайшим POP, которое нельзя оптимизировать программно. Мы задокументировали полный бюджет задержки и измерения каждого региона в статье про p95 менее 15мс.

Размещение поиска на пограничных серверах, а не на одном центральном сервере - это разница между редиректом, ощущающимся мгновенным, и тем, который добавляет заметную задержку. Это также причина, по которой anycast-маршрутизация на пограничных серверах превосходит настройку только через DNS для этой нагрузки - сравнение, которое мы разбираем в статье пограничные POP против маршрутизации только через DNS. Коротко: сеть занимается географией, кэш занимается скоростью.

Подсчёт клика без замедления редиректа#

Сокращатель, который только перенаправлял бы, был бы сервисом редиректа. Инструментом управления ссылками его делает подсчёт каждого клика и информация о том, кто кликнул, откуда и с какого устройства. Сложность в том, чтобы делать это без задержки для посетителя.

Ответ - полностью разделить эти два процесса. Когда обработчик редиректа разрешает слаг, он немедленно отправляет ответ 302. Запись клика происходит после, как работа «выстрелил и забыл». Обработчик добавляет событие клика - слаг, временную метку, усечённый IP, хэш user-agent - в очередь сообщений и продолжает работу. Он не ждёт подтверждения записи. Если очередь временно недоступна, клик теряется, а не задерживается редирект; мы сделали осознанный выбор: потеря клика при сбое инфраструктуры допустима, а сбой редиректа - нет.

Elido использует Redpanda в качестве этой очереди. Отдельный потребитель, обработчик кликов, читает события из очереди и записывает их в ClickHouse - колоночную базу данных, созданную для высокообъёмной нагрузки на добавление и агрегацию, характерной для аналитики кликов. Редирект посетителя завершился миллисекунды назад; строка аналитики появляется несколько секунд спустя, полностью вне горячего пути. Мы объясняем архитектуру очереди в статье асинхронная обработка кликов с Redpanda и почему колоночное хранилище лучше Postgres для этого - в статье почему мы используем ClickHouse для аналитики кликов.

Именно это разделение позволяет вашей аналитике быть детальной без замедления. Путь редиректа остаётся лёгким, потому что ни подсчёт, ни оценка, ни агрегация не происходят, пока посетитель ждёт.

Всё вместе#

Сокращатель URL, от начала до конца, - короткая последовательность. Вы создаёте ссылку, и сокращатель сохраняет соответствие слага и адреса назначения в Postgres, генерируя или проверяя слаг как уникальный ключ. Посетитель нажимает, запрос попадает на ближайший пограничный POP, и обработчик разрешает слаг из кэша в памяти, обращаясь к Redis и затем к базе данных только при промахе. Он возвращает 302 с адресом назначения в заголовке Location, так что клик можно посчитать, а адрес назначения остаётся редактируемым. Затем он отправляет событие клика в очередь для отдельного потребителя, не задерживая никого.

Каждая часть проста сама по себе. Инженерия - в соотношениях и бюджетах: нагрузка с преобладанием чтений, которой нужен кэш; потолок задержки, которому нужен пограничный сервер; и требования аналитики, которые нужно держать вне горячего пути. Если вы хотите строить поверх этого, функция умных ссылок открывает уровень правил, API и SDK позволяют создавать ссылки из кода, а страница решений для разработчиков и документация по архитектуре edge-redirect рассматривают детали глубже. Если вы оцениваете инструмент, а не строите его, наши статьи о том, что такое сокращатель URL и безопасны ли сокращатели URL освещают тему со стороны пользователя, а страница тарифов показывает, где заканчивается бесплатный уровень.

Читайте также в блоге#

Попробуйте Elido

URL-сокращатель с хостингом в ЕС: собственные домены, глубокая аналитика, открытый API. Бесплатный тариф - без банковской карты.

Теги
how do url shorteners work
url shortener mechanics
301 vs 302 redirect
url shortener database
short link lookup
url shortener architecture

Читать дальше