8 min czytaniaInżynieria

Jak zbudować skracacz URL: architektura i kod

Jak zbudować skracacz URL, który przetrwa produkcję: generowanie krótkich kodów, ścieżka przekierowania, buforowanie, śledzenie kliknięć, ochrona przed nadużyciami i co utrzymywać.

Marius Voß
DevRel · edge infra
Diagram architektury skracacza URL pokazujący ścieżkę zapisu, która koduje krótki kod, oraz ścieżkę odczytu, która rozwiązuje przekierowanie z pamięci podręcznej

Aby zbudować skracacz URL, potrzebujesz czterech rzeczy: miejsca do przechowywania mapowania krótkiego kodu na docelowy URL, sposobu generowania unikalnego kodu dla każdego nowego linku, obsługi przekierowania, która wyszukuje kod i zwraca przekierowanie HTTP, oraz pamięci podręcznej przed wyszukiwaniem, ponieważ odczyty przewyższają zapisy w znacznym stosunku. To jest cały rdzeń i możesz go uruchomić w ciągu popołudnia.

Pułapką jest myślenie, że wersja na popołudnie to gotowy produkt. Przekierowanie działające na Twoim laptopie a serwis skracania URL, który przetrwa, gdy nieznajomi kierują go na złośliwe oprogramowanie, bombardują go ruchem i oczekują czterech dziewiątek dostępności, to różne problemy inżynieryjne. Pierwsze to algorytm. Drugie to zobowiązanie operacyjne.

Ten przewodnik buduje rdzeń uczciwie, a następnie większość czasu poświęca na część, którą samouczki do projektowania systemów pomijają: to, co nadal musisz zbudować po tym, jak przekierowanie działa. Jeśli chcesz najpierw przeczytać koncepcyjne wprowadzenie, jak działają skracacze URL opisuje mechanikę bez kodu.

Dwie ścieżki w skracaczu URL: ścieżka zapisu koduje unikalny ID w krótki kod i zapisuje go, ścieżka odczytu rozwiązuje kliknięcie przez pamięć podręczną do przekierowania

Krótka wersja: co naprawdę robi skracacz URL#

Skracacz URL to wyszukiwanie klucz-wartość noszące przekierowanie HTTP. Klucz to krótki kod, wartość to długi URL, a całe zadanie polega na zamianie example.com/aB3x9 na 302 wskazujące na oryginalny adres.

Model danych to jedna tabela:

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);

Przez nią przebiegają dwie ścieżki. Ścieżka zapisu pobiera długi URL, generuje krótki kod i wstawia wiersz. Ścieżka odczytu pobiera krótki kod, wyszukuje wiersz i zwraca przekierowanie. Odczyty dominują w stosunku, który zwykle wynosi około 1000 do 1, więc prawie cała Twoja uwaga inżynierska powinna skupiać się na szybkim i tanim wyszukiwaniu. Unikalny indeks na short_code sprawia, że to wyszukiwanie jest przeszukiwaniem indeksu, a nie skanem. To cały rdzeń.

Generowanie krótkiego kodu: base62, losowy lub hash#

Krótki kod to miejsce, gdzie leży interesująca decyzja. Masz trzy realistyczne strategie, które kompromisowo balansują długość, przewidywalność i trudność obsługi kolizji.

Base62 z unikalnego ID to klasyka. Weź automatycznie inkrementujące ID wiersza i zakoduj je w base62, 62 znaki a-z, A-Z i 0-9. Kody są krótkie, nigdy nie kolidują, ponieważ każde ID jest unikalne, i stają się o jeden znak dłuższe mniej więcej co 62x pod względem wolumenu. Wadą jest to, że są sekwencyjne i przewidywalne, więc każdy może przejść przez Twój przestrzeń nazw.

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)
}

Losowe ciągi znaków rozwiązują problem przewidywalności. Wygeneruj krótki losowy kod, na przykład za pomocą biblioteki takiej jak nanoid, i sprawdź go względem unikalnego indeksu przed zapisaniem. Przy siedmiu znakach base62 masz biliony możliwości, więc kolizje są rzadkie, ale nadal musisz obsłużyć rzadkie wstawienie, które nie spełnia ograniczenia unikalności, ponownie próbując z nowym kodem.

Haszowanie URL to trzecia opcja i zazwyczaj najgorsza. Hash długiego URL jest deterministyczny, co brzmi wygodnie, ale nadal musisz go obcinać, nadal otrzymujesz kolizje, a identyczne URL mapują na identyczne kody, co ujawnia informacje. Większość serwisów produkcyjnych wybiera base62 dla wewnętrznych ID lub losowe kody dla publicznych. Niestandardowe lub markowe slug-i, kody wpisywane przez użytkownika ręcznie, są weryfikowane względem tego samego unikalnego indeksu przed ich zaakceptowaniem.

Ścieżka przekierowania: 301 vs 302 i dlaczego to decyduje o Twojej analityce#

Kod statusu przekierowania to nie kosmetyczny wybór. Decyduje o tym, czy kiedykolwiek zobaczysz drugie kliknięcie.

301 Moved Permanently mówi przeglądarkom i serwerom proxy, że przeniesienie jest trwałe, więc buforują je. Po pierwszej wizycie przeglądarka może wysyłać przyszłe kliknięcia bezpośrednio do miejsca docelowego bez dotykania Twojego serwera. Świetne dla czystej prędkości, fatalne dla analityki, ponieważ kliknięcia, które najbardziej chcesz liczyć, to te, które nigdy do Ciebie nie docierają. Semantyka HTTP jest opisana w RFC 9110, który definiuje zarówno trwałe, jak i tymczasowe przekierowania.

302 Found lub 307 Temporary Redirect jest ponownie żądany za każdym razem. Przeglądarka pyta Twój serwer przy każdym kliknięciu, co oznacza, że możesz liczyć każdą wizytę i możesz później zmienić miejsce docelowe bez walki z przestarzałymi pamięciami podręcznymi. Dla skracacza linków, którego całą wartością są edytowalne linki i dane o kliknięciach, to jest właściwe domyślne ustawienie. Kosztem jest jedno sieciowe przejście w obie strony na kliknięcie, co trafienie w pamięć podręczną czyni zaniedbywalnym.

Zasada kciuka: sięgaj po 302, chyba że masz konkretny powód, aby chcieć link zamrożony i buforowany na zawsze. Post przekierowania 301 vs 302 szczegółowo opisuje kompromis, a typy przekierowań obejmuje resztę rodziny 3xx, w tym kiedy ważne są 307 i 308.

Przechowywanie i buforowanie: projektowanie dla stosunku odczyt/zapis 1000:1#

Ponieważ odczyty zalewają zapisy, baza danych nie jest Twoim wąskim gardłem - jest nim Twoja strategia buforowania. Wzorzec to pamięć podręczna odczytu: przy kliknięciu najpierw sprawdź pamięć podręczną w pamięci i wróć do bazy danych dopiero przy braku trafienia, zapisując wynik z powrotem do pamięci podręcznej na następny raz.

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
}

W produkcji zwykle staje się to dwuwarstwowe: mała pamięć podręczna w procesie dla najgorętszych linków, zabezpieczona współdzielonym magazynem w pamięci, takim jak Redis, dzięki czemu każda instancja serwera korzysta z wyszukiwania, które już wykonała dowolna z nich. Baza danych, źródło prawdy, jest dotykana tylko przy prawdziwym zimnym brakiem trafienia. Uzyskaj tę warstwę właściwie i jeden skromny serwer obsługuje ogromny wolumen kliknięć. Post strategia buforowania dla przekierowań URL zagłębia się w decyzje dotyczące eksmisji i rozmiaru, a kluczowy post na temat osiągania p95 poniżej 15ms opisuje, jak wygląda dostrojona ścieżka przekierowania pod obciążeniem.

Jeśli wolisz nie uruchamiać żadnego z tego, API Elido zapewnia Ci warstwę przekierowania, pamięć podręczną i dostarczanie w regionie EU z p95 poniżej 15ms przy trafieniu w pamięć podręczną, za pomocą jednego wywołania. Zacznij za darmo i pomiń operacje.

Liczenie kliknięć bez spowalniania przekierowania#

Błąd, który niszczy latencję przekierowania, to zapisywanie kliknięcia do bazy danych wewnątrz obsługi przekierowania. Zrób to, a każdy odwiedzający będzie czekał na zapis analityczny przed otrzymaniem przekierowania.

Oddziel je. Obsługa natychmiast emituje przekierowanie, a następnie wysyła zdarzenie kliknięcia do trwałego dziennika lub kolejki komunikatów jako pracę "fire-and-forget". Oddzielny konsument czyta ten strumień i zapisuje zdarzenia do magazynu analitycznego według własnego harmonogramu. Odwiedzający nigdy nie czeka, a zapytanie raportowe, które skanuje miliony wierszy kliknięć, nigdy nie konkuruje z ścieżką przekierowania o zasoby. Kolumnowa baza danych analitycznych obsługuje te zapytania agregujące znacznie lepiej niż magazyn wierszy, dlatego zdarzenia kliknięć zwykle trafiają gdzie indziej niż tabela linków. Post fire-and-forget click ingestion szczegółowo opisuje stronę kolejkową, a why a columnar store beats Postgres for click analytics wyjaśnia wybór przechowywania. Analityka Elido ma taki kształt, więc kliknięcia są dostępne do zapytań w sekundach bez dodawania milisekund do przekierowania.

Zaległości produkcyjne poza działającym przekierowaniem: skanowanie nadużyć, ograniczanie szybkości, TLS niestandardowych domen, dane o kliknięciach bezpieczne dla GDPR i wysoka dostępność

Co jeszcze musisz zbudować: trudne 80 procent#

Oto część, którą samouczki do projektowania systemów pomijają. Działające przekierowanie to może jedna piąta prawdziwego serwisu skracania URL. Reszta to wszystko, co zamienia demo w coś, co możesz umieścić w publicznym internecie.

  • Skanowanie nadużyć i bezpieczeństwa. Publiczny skracacz staje się magnesem na phishing w ciągu kilku godzin od uruchomienia. Musisz sprawdzać miejsca docelowe względem kanału zagrożeń, takiego jak Google Safe Browsing, i ponownie skanować, ponieważ czysty URL podczas tworzenia może później stać się złośliwy. Lista kontrolna bezpieczeństwa skracacza URL to pełna lista.
  • Ograniczanie szybkości i idempotentność. Otwarty punkt końcowy tworzenia jest natychmiast skryptowany. Potrzebujesz limitów na klucz i idempotentności, aby powtórzone żądanie nie tworzyło zduplikowanych linków. Mechanika jest opisana w limity szybkości API i idempotentność.
  • Niestandardowe domeny z TLS. Markowe linki oznaczają wydawanie certyfikatów dla domen, których nie posiadasz, na żądanie, bez ręcznych kroków.
  • Dane o kliknięciach bezpieczne dla GDPR. W momencie gdy rejestrujesz kliknięcia, przetwarzasz dane osobowe. Skracanie adresów IP i dokumentowanie retencji nie jest opcjonalne w UE, jak opisuje GDPR dla skraczy URL.
  • Wysoka dostępność. Twoje przekierowanie jest teraz na krytycznej ścieżce każdego linku, który ktokolwiek udostępnił. Przestój psuje treść innych osób, więc poprzeczka dostępności jest wyższa niż w przypadku większości aplikacji.

Żadne z tych zagadnień nie jest egzotyczne. To po prostu dużo trwałej pracy, która nigdy się nie kończy, i to jest uczciwy powód, dla którego większość zespołów zatrzymuje się na MVP i sięga po coś utrzymywanego.

Budować, kupować czy samodzielnie hostować#

Budowanie samodzielnie to najlepszy sposób na zrozumienie przekierowań, kodowania i buforowania, a dla zamkniętego narzędzia wewnętrznego MVP może być wszystkim, czego kiedykolwiek potrzebujesz. Zbuduj to. Nauczysz się więcej w weekend niż jakiekolwiek przygotowanie do rozmów kwalifikacyjnych Ci da.

W przypadku wszystkiego publicznego lub biznesowego, uczciwie rozważ koszty utrzymania. Przekierowanie jest bezpłatne; obsługa nadużyć, TLS dla niestandardowych domen, potok analityczny i rotacja dyżurów - nie są. Jeśli chcesz kontrolę bez pisania od zera, możesz samodzielnie hostować istniejący serwis, a Elido dostarcza ścieżkę samodzielnego hostowania dokładnie do tego, przy czym post opcje open-source zestawia je obok siebie. Jeśli wolisz całkowicie to oddać, rozwiązanie dla deweloperów i quickstart API i SDK dają Ci produkcyjną warstwę przekierowania bez zaległości powyżej.

Powiązane na blogu#

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.

Za darmo, bez rejestracji · 2 dziennie

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan - bez karty kredytowej.

Tagi
build a url shortener
url shortener system design
short code generation
base62 encoding
url redirect
url shortener architecture
link shortener api

Czytaj dalej