URL kısaltıcı bir hafta sonu projesi gibi görünür: uzun bir URL alırsınız, kısa bir kod verirsiniz, biri tıkladığında yönlendirirsiniz. Bu yüzden bu kadar çok sistem tasarımı mülakatında karşınıza çıkar. Küçük yüzeyin altında ID üretimi, önbellek, HTTP semantiği ve analitik hakkında gerçek kararlar yatar ve her biri mimariyi değiştirir.
Bu yazı uçtan uca bir tane tasarlıyor. Sayılarla başlıyor, çakışma olmadan kısa kod üretmenin yollarını inceliyor, 301 mi 302 mi sorusunu karara bağlıyor ve kopyalayıp uyarlayabileceğiniz bir mimari diyagramla bitiyor.
Gereksinimler ve sayılar
Fonksiyonel gereksinimler:
- Uzun bir URL için kısa bağlantı oluşturmak, isteğe bağlı olarak özel bir takma adla.
- Kısa bağlantıyı uzun URL'ye yönlendirmek.
- Bağlantı başına tıklamaları saymak.
- İstendiğinde bağlantıların süresini doldurmak.
Fonksiyonel olmayan gereksinimler: yönlendirmeler hızlı ve yüksek erişilebilir olmalı, bağlantılar özel olabiliyorsa kısa kodlar sırayla tahmin edilememeli ve sistem oltalama sayfaları için ücretsiz bir yönlendiriciye dönüşmemeli.
Kaba hesaplar tasarımın şeklini netleştirir:
| Varsayım | Değer | Saniye başına |
|---|---|---|
| Aylık yeni bağlantı | 100 milyon | yaklaşık 40 yazma/sn |
| Aylık yönlendirme | 10 milyar | yaklaşık 3.900 okuma/sn |
| Okuma/yazma oranı | 100:1 | |
| Kayıt boyutu | yaklaşık 500 bayt | |
| 5 yıllık depolama | 6 milyar bağlantı | yaklaşık 3 TB |
Okumalar yazmalardan iki kat büyüklük mertebesi fazla; bu yüzden önbellek, replikalar ve özen okuma yoluna gider. Yazma yolu küçüktür ve basit kalabilir.
API
Ürünü iki endpoint taşır:
POST /links
Content-Type: application/json
{"url": "https://example.com/very/long/path", "alias": "launch", "expiresAt": "2027-01-01T00:00:00Z"}
201 Created
{"code": "launch", "shortUrl": "https://sho.rt/launch"}
GET /launch
302 Found
Location: https://example.com/very/long/path
Bunları ayrı servislerde tutun. Yönlendirme servisi durumsuz ve salt okunurdur; onu tek başına ölçekleyebilir ve seyrek dağıtabilirsiniz.
Kısa kodu üretmek
Kısa kod, base62 (a–z, A–Z, 0–9) ile yazılmış bir sayıdır. Yedi karakter 62^7, yani yaklaşık 3,5 trilyon kod verir; altı karakter yaklaşık 56,8 milyar. Ayda 100 milyon bağlantıyla yedi karakter binlerce yıl yeter.
Asıl soru sayının nereden geldiğidir:
| Yaklaşım | Nasıl çalışır | Dikkat edilecekler |
|---|---|---|
| Sayaç + base62 | Tek bir global dizi, kodlanmış hâli | Tek bir çekişme noktası; kodlar sıralı ve tahmin edilebilir |
| ID aralıkları | Bir ayırıcı her API örneğine bir ID bloğu verir (örneğin 100.000) | Bir örnek ölünce boşluklar oluşur; blok içinde hâlâ sıralı |
| Hash + kırpma | URL'yi hash'leyip ilk 7 base62 karakterini tutmak | Çakışmalar; kontrol edip yeniden denemeniz gerekir |
| Snowflake tarzı ID'ler | 64 bitte zaman damgası + makine ID'si + sıra | Daha uzun kodlar (base62'de 11 karakter) |
Hash cazip görünür, çünkü aynı URL her zaman aynı kodu alır. Sorun doğum günü problemidir: 3,5 trilyon olası kodla, en az bir çakışma olasılığı yalnızca yaklaşık 2,2 milyon bağlantıdan sonra %50'yi geçer. Bundan sonra her ekleme bir benzersizlik kontrolü ve tuzla yeniden deneme ister; bu da bir yazmayı bir okuma artı bir yazmaya çevirir.
ID aralıkları pragmatik varsayılandır. Her API örneği ayırıcıdan küçük bir işlemle bir blok ayırır, sonra kodları hiçbir koordinasyon olmadan bellekten dağıtır. Sıralı kod istemiyorsanız ID'yi kodlamadan önce tersine çevrilebilir bir permütasyondan geçirin (örneğin ID uzayı üzerinde bir blok şifre). Twitter'ın Snowflake projesi diğer yolu gösterir: 41 bitlik zaman damgası, 10 bitlik makine ID'si ve 12 bitlik sıra, merkezi bir ayırıcı olmadan benzersiz 64 bitlik ID'ler üretir; bedeli daha uzun kodlardır.
Özel takma adlar üreteci atlar. Onları aynı tabloda saklayın ve kopyaları reddetmek için kod sütunundaki benzersizlik kısıtına güvenin.
301 mi 302 mi?
Yönlendirme kodlarını RFC 9110 tanımlar. Burada önemli olan fark önbelleklemedir: 301 (Moved Permanently) yanıtı sezgisel olarak önbelleğe alınabilir; tarayıcılar onu hatırlayıp bir sonraki tıklamada sunucunuzu atlayabilir. 302 (Found) geçicidir ve siz belirtmedikçe önbelleğe alınmaz.
- Her tıklamanın sayılması gerekiyorsa ya da hedefi daha sonra değiştirmek istiyorsanız 302 kullanın.
- Bağlantı hiç değişmiyorsa ve eksiksiz analitik yerine daha az isteği tercih ediyorsanız 301 kullanın.
307 ve 308, 302 ve 301'in metodu koruyan sürümleridir. GET isteği olan sıradan tarayıcı tıklamalarında aralarındaki seçim nadiren önemlidir.
Depolama ve önbellek
Her yönlendirme koda göre tek bir anahtar aramasıdır; bu yüzden hızlı anahtar araması yapan her depo işinizi görür: DynamoDB ya da Cassandra gibi bir anahtar-değer deposu veya kodun birincil anahtar olduğu Postgres. Tek düğüm yetmediğinde koda göre shard'layın; tasarım gereği hiçbir kod diğerinden daha sıcak olmadığı için kodun hash'i yükü eşit dağıtır. İstisna popüler bağlantılardır ve onları önbellek karşılar.
Deponun önüne cache-aside okumayla bir Redis önbelleği koyun: kodu Redis'te arayın, ıskalamada veritabanına düşün, sonra önbelleği bir TTL ile doldurun. Bağlantı popülerliği uzun kuyruk dağılımı izler; bu yüzden bağlantıların küçük bir kısmını tutan bir önbellek tıklamaların çoğunu karşılar. Önbellek stratejileri yazısı TTL'leri, stampede'leri ve geçersiz kılmayı ayrıntılı anlatır. Okuma replikaları geri dönüş yoluna yardım eder; veritabanı replikasyonu yazısı o zaman katlanmanız gereken gecikmeyi açıklar.
Bir Cache-Control başlığı gönderirseniz bir CDN ya da uç önbellek en popüler yönlendirmeleri doğrudan sunabilir; ama o zaman uç katman, sunucularınızın hiç görmediği tıklamaları yanıtlar. Ya uçta loglayın ya da yönlendirmeleri önbelleğe almayın.
Yönlendirmeyi yavaşlatmadan tıklama analitiği
Yönlendirme yolunda tıklama sayısını asla senkron yazmayın. Yönlendirme servisi küçük bir tıklama olayını (kod, zaman damgası, referrer, ülke) Kafka gibi bir loga yayınlar ve hemen yanıt döner. Bir tüketici sayıları bağlantı ve gün başına toplar ve bir analitik deposuna yazar. Analitik tarafı geride kalırsa yönlendirmeler çalışmaya devam eder; bu, olay güdümlü mimari yazısında anlatılan ayrıştırmanın aynısıdır.
Mimari diyagram
Diyagram içeriği
- User
- Edge / CDN
- Load balancer
- Redirect service
- Shorten API
- Redis: code to URL
- Links store
- ID allocator
- URL reputation API
- Click events
- Click aggregator
- Analytics store
- GET /{code}
- POST /links
- 1. lookup
- 2. on miss
- insert code and URL
- ID range
- check the target
- click event
- consume
- daily counts
Nasıl okunur:
- Okuma yolu (üstte).
GET /{code}istekleri uç katmanı ve yük dengeleyiciyi geçip yönlendirme servisine gelir; servis önce Redis'e, yalnızca ıskalamada bağlantı deposuna bakar. - Yazma yolu (altta).
POST /linkskısaltma API'sine gider. API hedefi bir URL itibar servisiyle kontrol eder, ayırıcıdan ayırdığı aralıktan ID alır, bunları base62 ile kodlar ve bağlantıyı yazar. - Analitik (sağda). Tıklama olayları asenkron olduğu için kesiklidir. Toplayıcı onları günlük sayılara çevirir.
- Markdown bloğu kapasite sayılarını tasarımın yanında tutar; böylece gözden geçiren kişi onları kontrol edebilir.
Kötüye kullanım, süre dolumu ve özel takma adlar
Herkese açık bir kısaltıcı, siz durdurmadıkça bir oltalama aracıdır. Yeni hedefleri Google Safe Browsing gibi bir itibar servisine karşı kontrol edin, bağlantı oluşturmayı hesap ve IP başına sınırlayın ve bir kodu hızla devre dışı bırakmanın bir yolunu tutun. Eski bağlantıları da belirli aralıklarla yeniden kontrol edin; temiz bir alan adı sonradan zararlıya dönüşebilir.
Süre dolumu için expiresAt değerini bağlantıyla birlikte saklayın ve yönlendirmede kontrol edin, sonra süresi dolan satırları bir arka plan işiyle silin. Süresi dolmuş kodlar için yönlendirmek yerine 404 ya da 410 döndürün.
Diyagrama neler koymalı
- Okuma ve yazma yollarını ayırın. Farklı ölçeklenirler ve diyagram bunu göstermeli.
- Yönlendirme durum kodunu (301 ya da 302) yönlendirme servisinin üzerine yazın. Analitiğin eksiksiz olup olmayacağını o belirler.
- ID'lerin nereden geldiğini gösterin. Bir kutuya yazılmış "Kod üret", tasarımın en çok tartışılan kararını gizler.
- Asenkron akışları kesikli çizin. Okuyan, analitiğin bir yönlendirmeyi yavaşlatamayacağını bir bakışta görmeli.
- Sayıları yazın. Saniye başına yazma ve depolama boyutu olmayan bir tasarım gözden geçirilemez.
Sistem tasarımı diyagramı örneği aynı kuralları daha büyük bir sistemde gösterir.
Archboard'da oluşturun
- Google ya da GitHub ile giriş yapın ve bir diyagram oluşturun.
- Kullanıcı için C4 Modeli paketinden bir Kişi, Bulut paketinden bir CDN ve bir Bulut yük dengeleyici ekleyin.
- C4 Modeli paketinden dört Konteyner düğümü ekleyin: yönlendirme servisi, kısaltma API'si, ID ayırıcı ve tıklama toplayıcı. Her birine teknolojiyi ve tek satırlık bir açıklama yazın.
- Veri paketinden Redis, Kafka ve bir Veri ambarı, bağlantı deposu için C4 Modeli paketinden bir Veritabanı ve URL itibar API'si için bir Harici sistem ekleyin.
- Okuma yolunu üstte, yazma yolunu altta bağlayın, tıklama olaylarını kesikli kenarlarla çizin ve kapasite sayılarını içeren, Temel paketinden bir Markdown bloğu ekleyin.
Örnek 13 diyagram düğümü kullanıyor ve Free planın sınırları içinde kalıyor.
SSS
Kısa kod ne kadar uzun olmalı? Yedi base62 karakteri yaklaşık 3,5 trilyon kod verir; ayda 100 milyon bağlantıyla onlarca yıl yeter. Yediyle başlayın ve kodların ileride sekize çıkmasına izin verin; tasarımda hiçbir şey sabit bir uzunluğa bağlı değil.
Kodu almak için uzun URL'yi hash'lemeli miyim? Yalnızca tekilleştirme basit yazmalardan daha önemliyse. Kırpılmış hash'ler erken çakışır (7 karakterde 2,2 milyon bağlantıdan sonra en az bir çakışma olasılığı yarıdan fazladır), bu yüzden her ekleme bir kontrol ve yeniden deneme ister.
URL kısaltıcı için 301 mi daha iyi, 302 mi? Her tıklamanın sayılması gerekiyorsa ya da hedefi değiştirebilecekseniz 302. 301 tarayıcıların yönlendirmeyi önbelleğe almasına izin verir; bu istek tasarrufu sağlar ama tekrar eden tıklamaları analitiğinizden gizler.
Bağlantıları hangi veritabanı saklamalı? Birincil anahtara göre hızlı arama yapan herhangi bir depo. Bu sayılarda Postgres uzun süre yeter; yazmaları birçok düğüme shard'lamanız gerektiğinde bir anahtar-değer deposu cazip hâle gelir.
İnsanların bağlantıları tahmin etmesini nasıl engellerim? Sıralı ID'leri dışarı açmayın. ID'yi kodlamadan önce permüte edin ya da benzersizlik kontrolüyle rastgele kodlar kullanın ve kısa bağlantıyı özel içerik için asla bir sır gibi görmeyin.