Чтобы создать сокращатель URL, вам нужны четыре вещи: место для хранения маппинга короткого кода на целевой URL, способ генерации уникального кода для каждой новой ссылки, обработчик редиректа, который ищет код и возвращает HTTP-редирект, и кеш перед поисковым запросом, потому что чтений значительно больше, чем записей. Это всё ядро, и его можно поднять за afternoon.
Ловушка - думать, что версия, собранная за afternoon, это уже продукт. Редирект, работающий на вашем ноутбуке, и сервис сокращения URL, выдерживающий незнакомцев, направляющих его на вредоносное ПО, нагружающих трафиком и ожидающих четырёх девяток доступности, - это разные инженерные задачи. Первое - алгоритм. Второе - операционное обязательство.
Это руководство честно строит ядро, а затем большую часть времени тратит на то, что пропускают туториалы по системному дизайну: что ещё нужно построить после того, как редирект заработал. Если сначала хотите концептуальное введение, статья как работают сокращатели URL объясняет механику без кода.
Кратко: что на самом деле делает сокращатель URL#
Сокращатель URL - это поиск по ключу-значению с HTTP-редиректом. Ключ - это короткий код, значение - длинный URL, и вся задача сводится к превращению example.com/aB3x9 в 302, указывающий на исходный адрес.
Модель данных - одна таблица:
CREATE TABLE links (
id BIGSERIAL PRIMARY KEY,
short_code TEXT NOT NULL UNIQUE,
long_url TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX idx_links_short_code ON links (short_code);
Через неё проходят два пути. Путь записи принимает длинный URL, генерирует короткий код и вставляет строку. Путь чтения принимает короткий код, ищет строку и возвращает редирект. Чтения преобладают в соотношении примерно 1000 к 1, поэтому почти всё инженерное внимание должно быть направлено на то, чтобы поиск был быстрым и дешёвым. Уникальный индекс на short_code - это то, что делает поиск поиском по индексу, а не полным сканированием. Вот и всё ядро.
Генерация короткого кода: Base62, случайный или хеш#
Короткий код - место, где находится интересное решение. Есть три реалистичные стратегии, и они компромиссны по длине, предсказуемости и сложности обработки коллизий.
Base62 от уникального ID - классика. Берёте автоинкрементный ID строки и кодируете его в base62, 62 символа a-z, A-Z и 0-9. Коды короткие, коллизии невозможны, так как каждый ID уникален, и они становятся на один символ длиннее примерно каждые 62x по объёму. Недостаток - они последовательны и предсказуемы, поэтому любой может обойти ваше пространство имён.
const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// encode turns a positive integer ID into a base62 short code.
func encode(id uint64) string {
if id == 0 {
return string(alphabet[0])
}
var b []byte
for id > 0 {
b = append(b, alphabet[id%62])
id /= 62
}
// reverse, since we built the digits least-significant first
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}
Случайные строки решают проблему предсказуемости. Генерируете короткий случайный код, например с помощью библиотеки nanoid, и проверяете его по уникальному индексу перед сохранением. При семи символах base62 у вас триллионы возможностей, так что коллизии редки, но вы всё равно должны обрабатывать редкую вставку, которая нарушает ограничение уникальности, повторно пробуя с новым кодом.
Хеширование URL - третий вариант и обычно худший. Хеш длинного URL детерминирован, что кажется удобным, но вам всё равно нужно его усекать, вы всё равно получаете коллизии, и одинаковые URL отображаются на одинаковые коды, что раскрывает информацию. Большинство продакшн-сервисов выбирают base62 для внутренних ID или случайные коды для публичных. Кастомные или брендированные слаги, коды, которые пользователь вводит вручную, проверяются по тому же уникальному индексу перед принятием.
Путь редиректа: 301 против 302 и почему это определяет вашу аналитику#
Код статуса редиректа - не косметический выбор. Он определяет, увидите ли вы второй клик.
301 Moved Permanently сообщает браузерам и прокси, что перемещение постоянное, поэтому они кешируют его. После первого посещения браузер может направлять будущие клики прямо к месту назначения, не обращаясь к вашему серверу. Отлично для скорости, но губительно для аналитики, потому что клики, которые вы больше всего хотите считать, это те, которые никогда до вас не доходят. HTTP-семантика прописана в RFC 9110, который определяет как постоянные, так и временные редиректы.
302 Found или 307 Temporary Redirect запрашивается каждый раз. Браузер обращается к вашему серверу при каждом клике, что означает возможность считать каждое посещение и изменять место назначения позже без борьбы с устаревшими кешами. Для сокращателя ссылок, вся ценность которого - в редактируемых ссылках и данных о кликах, это правильный вариант по умолчанию. Цена - один сетевой round trip на клик, который попадание в кеш делает незначительным.
Практическое правило: используйте 302, если нет конкретной причины хотеть, чтобы ссылка была заморожена и кеширована навсегда. Пост 301 против 302 редиректов подробно разбирает этот компромисс, а типы редиректов охватывает остальных представителей семейства 3xx, включая случаи, когда важны 307 и 308.
Хранение и кеширование: проектирование для соотношения чтения/записи 1000:1#
Поскольку чтения значительно перевешивают записи, узким местом является не база данных, а стратегия кеширования. Паттерн - сквозной кеш для чтения: при клике сначала проверяете кеш в памяти, и обращаетесь к базе данных только при промахе, записывая результат обратно в кеш для следующего раза.
func resolve(ctx context.Context, code string) (string, error) {
if url, ok := cache.Get(code); ok {
return url, nil // hot path: served from memory
}
url, err := db.LookupLongURL(ctx, code)
if err != nil {
return "", err
}
cache.Set(code, url) // populate for the next click
return url, nil
}
В продакшне это обычно становится двухуровневым: небольшой внутрипроцессный кеш для самых горячих ссылок, поддерживаемый общим хранилищем в памяти, например Redis, чтобы каждый экземпляр сервера получал пользу от поиска, выполненного любым из них. База данных, источник истины, затрагивается только при реальном холодном промахе. Настройте этот слой правильно, и один скромный сервер обработает огромный объём кликов. Пост стратегия кеширования для URL-редиректов подробно разбирает решения по вытеснению и размерам, а краеугольная статья о достижении p95 менее 15 мс показывает, как выглядит настроенный путь редиректа под нагрузкой.
Если вы предпочитаете не запускать всё это самостоятельно, API Elido предоставляет уровень редиректа, кеш и внутрирегиональную EU-доставку с p95 менее 15 мс при попадании в кеш - за один вызов. Начните бесплатно и избавьте себя от операционных вопросов.
Подсчёт кликов без замедления редиректа#
Ошибка, которая убивает латентность редиректа, - запись клика в базу данных внутри обработчика редиректа. Тогда каждый посетитель ждёт завершения аналитической записи, прежде чем получит редирект.
Разделите их. Обработчик немедленно выполняет редирект, а затем отправляет событие клика в надёжный журнал или очередь сообщений по принципу "выстрелил и забыл". Отдельный потребитель читает этот поток и записывает события в аналитическое хранилище по собственному расписанию. Посетитель никогда не ждёт, а аналитический запрос, сканирующий миллионы строк кликов, никогда не конкурирует с путём редиректа за ресурсы. Колоночная аналитическая база данных обрабатывает такие агрегатные запросы значительно лучше, чем строковые хранилища, вот почему события кликов обычно попадают куда-то отдельно от таблицы ссылок. Пост приём кликов по принципу "выстрелил и забыл" подробно описывает сторону очереди, а статья почему колоночное хранилище лучше Postgres для аналитики кликов объясняет выбор хранилища. Аналитика Elido следует этой схеме, поэтому клики можно запрашивать за секунды, не добавляя миллисекунд к редиректу.
Что ещё нужно построить: сложные 80 процентов#
Вот часть, которую пропускают руководства по системному дизайну. Рабочий редирект - это, возможно, пятая часть реального сервиса сокращения URL. Остальное - всё то, что превращает демо во что-то, что можно выставить в публичный интернет.
- Сканирование на злоупотребления и безопасность. Публичный сокращатель становится мишенью для фишинга в течение нескольких часов после запуска. Нужно проверять пункты назначения по базе угроз, например Google Safe Browsing, и повторно сканировать, потому что чистый URL при создании может позже стать вредоносным. Контрольный список безопасности сокращателя URL - полный перечень.
- Ограничение запросов и идемпотентность. Открытый эндпоинт создания ссылок немедленно подвергается скриптовым атакам. Нужны лимиты на ключ и идемпотентность, чтобы повторный запрос не создавал дублирующиеся ссылки. Механика - в статье ограничение API запросов и идемпотентность.
- Кастомные домены с TLS. Брендированные ссылки означают выпуск сертификатов для доменов, которыми вы не владеете, по требованию, без ручных шагов.
- GDPR-безопасные данные кликов. С момента, когда вы начинаете логировать клики, вы обрабатываете персональные данные. Усечение IP-адресов и документирование сроков хранения не опционально в EU, как показывает статья GDPR для сокращателей URL.
- Высокая доступность. Ваш редирект теперь находится на критическом пути каждой ссылки, которой кто-либо поделился. Простой ломает контент других людей, поэтому требования к доступности выше, чем для большинства приложений.
Ничто из этого не является экзотикой. Это просто большой объём непрерывной работы, которая никогда не заканчивается, - и это честная причина, по которой большинство команд останавливается на MVP и обращается к чему-то готовому.
Создать, купить или развернуть самостоятельно#
Создать собственный сокращатель - лучший способ понять редиректы, кодирование и кеширование, а для закрытого внутреннего инструмента MVP может быть всем, что когда-либо понадобится. Создайте его. За выходные узнаете больше, чем даст любая подготовка к собеседованию.
Для всего публичного или бизнес-ориентированного честно оцените расходы на поддержку. Редирект бесплатен; обработка злоупотреблений, TLS для кастомных доменов, аналитический пайплайн и дежурство - нет. Если хотите контроль без написания с нуля, можно развернуть существующий сервис: Elido предлагает путь для самостоятельного развёртывания, а пост опции с открытым исходным кодом сравнивает их. Если предпочитаете делегировать полностью, решение для разработчиков и быстрый старт с API и SDK дадут вам продакшн-уровень редиректа без бэклога выше.
Читайте также в блоге#
- Как работают сокращатели URL - концептуальное введение, код не требуется.
- Достижение p95 менее 15 мс для редиректов - инженерная основа настроенного пути редиректа.
- Стратегия кеширования для URL-редиректов - двухуровневый кеш в деталях.
- Приём кликов по принципу "выстрелил и забыл" - отделение аналитики от редиректа.
- Сокращатели URL с открытым исходным кодом - опции с открытым кодом, если не хотите создавать с нуля.
Попробуйте Elido
Вставьте URL - получите короткую ссылку
Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.
Бесплатно, без регистрации · 2 в день