İçeriğe atla

Bilet Rezervasyon Sistemi Tasarımı: Koltuk Tutma, Sanal Kuyruklar ve Çifte Satışın Önlenmesi

Büyük bir satış açılışına dayanan bilet altyapısı tasarlayın: sanal bekleme odası, süresi dolan koltuk tutma, çifte satışsız rezervasyon ve idempotent ödemeler.

· Kurucu ve geliştirici

Yayımlandı:

Hayranlar CDN'deki bekleme odasına gelir, imzalı token'la API ağ geçidine ve rezervasyon servisine alınır. Servis koltukları Postgres'te tutar, sıraları Redis'te izler, ödeme sağlayıcısını çağırır. Bir fonksiyon süresi dolan tutmaları bırakır; kuyruk bilet teslimatını besler.

Bir stadyumda 60.000 koltuk vardır. Büyük bir turnenin biletleri satışa çıktığında milyonlarca kişi aynı dakikada gelir ve hepsi aynı birkaç bin iyi koltuğu ister. Kasım 2022'de Ticketmaster, Eras Tour ön satışı sırasında önceki zirvesinin dört katı olan toplam 3,5 milyar sistem isteği bildirdi. 60.000 satırı saklamak önemsizdir. Bu satırlar için yarışan milyonlarca isteği adil biçimde ve aynı koltuğu asla iki kez satmadan yönetmek ise işin zor kısmıdır.

Bu yazı, böyle bir satış açılışı için bir bilet rezervasyon sistemi tasarlıyor: gereksinimler, trafiğin şekli, sanal bekleme odası, süresi dolan koltuk tutmalar, rezervasyon işlemi, ödemeler, sıcak koltuklar ve botlar; bir mimari diyagramıyla.

Gereksinimler

İşlevsel:

  • Etkinliklere ve müsaitliği gösteren koltuk haritasına göz atmak.
  • Koltuk seçmek ve ödeme yapılırken onları tutmak.
  • Ödeme yapmak, bilet almak ve ödeme gerçekleşmezse koltukları serbest bırakmak.

İşlevsel olmayan:

  • Bir koltuğu asla iki kez satmamak. Esnetilemeyecek tek kural budur.
  • İnsanlara adil davranmak: en hızlı yenileyen değil, varış sırası ya da önceden duyurulmuş rastgele bir çekiliş.
  • Satış açılışının ilk dakikalarında normalin bir ya da iki büyüklük mertebesi üzerindeki trafiğe dayanmak.
  • Hızlı ve net hata vermek: Hızlı bir “tükendi” yanıtı, dönüp duran bir sayfadan iyidir.

Satış açılışının şekli

60.000 koltuk ve başlangıçtan sonraki on dakika içinde gelen 2 milyon kişi varsayın. Bu, saniyede yaklaşık 3.300 yeni gelen ve koltuk başına 30'dan fazla kişi demektir. Hiçbir kapasite herkese bilet veremez. Sistemin asıl işi, aynı anda sürdürülebilir sayıda kişinin alışveriş yapmasına izin vermek, geri kalanına hızla hayır demek ve aşırı çekişme altında envanteri doğru tutmaktır.

Bu da tasarımı iki parçaya ayırır: kalabalığı karşılayan bir kapı ve yalnızca kaldırabileceği kadar alıcı gören bir rezervasyon çekirdeği.

Sanal bekleme odası

Rezervasyon akışının önüne bir kuyruk koyun:

  1. Başlangıçtan önce gelen hayranlar, erken gelen herkes arasında rastgele bir sıra alır; böylece 9:59'da yüz sekme açmak işe yaramaz. Daha sonra gelenler sırayla kuyruğa girer.
  2. Bekleme odası CDN'den sunulan statik bir sayfadır; böylece bekleyen milyonlarca tarayıcının maliyeti neredeyse sıfırdır.
  3. Hayranlar, ölçülen rezervasyon kapasitesine göre ayarlanmış bir hızla, örneğin dakikada 2.000 kişi, gruplar hâlinde içeri alınır.
  4. İçeri alınan her hayran, oturumuna bağlı, imzalı ve kısa ömürlü bir token alır. Rezervasyon API'si geçerli token'ı olmayan istekleri reddeder.

Cloudflare Waiting Room ve Queue-it gibi yönetilen ürünler bunu yapar. Kendi geliştirdiğiniz bir sürüm, varış zamanlarını skor olarak kullanan bir Redis sıralı kümesidir (sorted set); sıra, üyenin rank değeridir:

ZADD queue:evt42 1793606400123 session:9f2c    # score = arrival time in ms
ZRANK queue:evt42 session:9f2c                  # position, 0-based
ZPOPMIN queue:evt42 2000                        # admit the next batch

Erken havuz için varış zamanları yerine rastgele skorlar atayın. Her iki durumda da kuyruk, sınırsız bir sıçramayı sistemin geri kalanının boyutlandırıldığı sabit bir akışa çevirir.

Envanter modeli

Numaralı oturma düzeni koltuk başına bir satır gerektirir:

Sütun Örnek Amaç
event_id, seat_id evt42, S112-R7-14 Kimlik; birlikte benzersiz
status available, held, sold Mevcut durum
hold_id, held_until h_81f, zaman damgası Kimin tuttuğu ve ne zamana kadar
order_id ord_5521 Satıldığında atanır

Numarasız (genel giriş) etkinlikler daha basittir: tek tek koltuklar yerine fiyat bölgesi başına, koşullu bir güncellemeyle azaltılan bir sayaç.

Süresi dolan koltuk tutmalar

Tutma, bir alıcı ödeme yaparken koltukları birkaç dakikalığına ona ayırır. Tutmalar için en güvenli yer, satışları kaydeden aynı veritabanıdır; koşullu bir güncelleme kullanılarak:

UPDATE seats
SET status = 'held', hold_id = $3, held_until = now() + interval '10 minutes'
WHERE event_id = $1
  AND seat_id = ANY($2)
  AND (status = 'available' OR (status = 'held' AND held_until < now()));
-- If fewer rows changed than seats requested, roll back: someone else got one.

İki alıcı aynı koltuk için bu ifadeyi aynı milisaniyede gönderebilir. Veritabanı o satırdaki güncellemeleri sıraya koyar; biri satırı değiştirir, diğeri sıfır satır görür ve net bir “koltuk az önce alındı” yanıtı alır. Süresi dolan tutmalar aynı ifadede geri alınır; böylece bir tutmanın sona ermesi hiçbir zaman bir arka plan işinin zamanında çalışmasına bağlı olmaz. Örtüşen koltuk kümeleri için gelen iki istek zaman zaman deadlock'a girebilir; veritabanı bunu tespit edip birini iptal eder, bu yüzden deadlock hatasında bir kez yeniden deneyin.

Çok yüksek ölçekte bazı ekipler öne hızlı bir filtre koyar: Redis'te anahtarı yalnızca yoksa ve on dakikalık bir sona erme süresiyle ayarlayan SET seat:evt42:S112-R7-14 h_81f NX PX 600000. Veritabanından yük alır, ancak Redis replikasyonu asenkrondur, bu yüzden bir failover tutmaları kaybettirebilir. Redis'i bir optimizasyon olarak görün; kararı yine veritabanı verir.

Son savunma hattı, biletler tablosundaki (event_id, seat_id) üzerinde bir benzersizlik kısıtıdır. Yukarıdaki her katmanda bir hata olsa bile veritabanı ikinci bileti kesmeyi reddeder.

Sıcak koltuklar ve en iyi uygun koltuk

Herkes ön sıraları ister; bu yüzden binlerce istek aynı birkaç yüz satırı hedefler ve çoğu başarısız olur. Çekişmeyi üç teknik azaltır:

  • En iyi uygun koltuk. Herkesin haritadan seçmesine izin vermek yerine, alıcıların bir bölüm ve adet seçmesine izin verin ve koltukları sunucu tarafında SELECT … FOR UPDATE SKIP LOCKED ile atayın; böylece eşzamanlı istekler aynı satırlarda sıraya girmek yerine farklı satırları alır.
  • Envanteri bölüme göre bölümleyin; böylece farklı bölümler farklı satırları kilitler ve farklı veritabanı shard'larında durabilir.
  • Koltuk haritasını kısa bir süreyle önbelleğe alın ve birkaç saniye eski olabileceğini kabul edin. Müsaitlik konusunda yetkili olan harita değil, tutmadır.

Çift ücretlendirme olmadan ödeme

  1. Tutmaya bağlı, pending durumunda bir sipariş oluşturun.
  2. Ödeme sağlayıcısını, sipariş kimliğinden türetilmiş bir idempotency anahtarıyla çağırın. Örneğin Stripe'ta idempotency anahtarları en fazla 255 karakterdir ve aynı anahtarla yeniden denenen bir istek, tekrar ücret almak yerine ilk sonucu döndürür.
  3. Başarılı olursa tek bir veritabanı işleminde koltukları satıldı olarak işaretleyin, siparişe bağlayın ve siparişi ödendi olarak işaretleyin.
  4. Ödemenin başarılı olduğu ama servisinizin yanıtı hiç görmediği durumu yakalamak için sağlayıcının webhook'larını dinleyin.

Tutmanın sona ermesi ödemeyle yarışmamalıdır. Ödeme başladığında tutmayı uzatın ya da bekleyen siparişi olan koltukları sona ermenin dışında tutun. Aksi hâlde bir koltuğun süresi dolabilir, başka birine satılabilir ve ardından ilk ödemesi başarılı olabilir. Ödeme ağ geçidi entegrasyonu diyagramı webhook'ları ve mutabakatı ayrıntılı olarak ele alıyor.

Süresi dolan tutmaları serbest bırakmak

Tutmalar hem tembel olarak, yukarıdaki held_until < now() koşuluyla, hem de etkin olarak, birkaç saniyede bir çalışıp süresi dolan tutmaları müsait olarak işaretleyen ve koltuk haritasının güncellenmesini sağlayan bir temizleyiciyle sona erer. Sona ermeyi Redis keyspace bildirimleri üzerine kurmayın: Redis, Pub/Sub'ını “ateşle ve unut” olarak belgeler; bağlantısı kopan bir abone olayları kaçırır.

Botlar ve kötüye kullanım

Bekleme odası ana savunmadır, çünkü hızı önemsiz kılar. Buna hesap başına satın alma limitleri; hesap, IP ve cihaz başına hız limitleri; yüksek talep gören etkinlikler için satış açılışından önce kayıt ve bekleme odasında doğrulama sınamaları ekleyin. Rate limiter sistem tasarımı sınırlamanın kendisini ele alıyor.

Bilet rezervasyon diyagramı

Hayranlar CDN'deki bekleme odasına gelir, imzalı token'la API ağ geçidine ve rezervasyon servisine alınır. Servis koltukları Postgres'te tutar, sıraları Redis'te izler, ödeme sağlayıcısını çağırır. Bir fonksiyon süresi dolan tutmaları bırakır; kuyruk bilet teslimatını besler.

11 bileşen · 11 bağlantı

Archboard'da aç
Diyagram içeriği
  • Fans
  • Waiting room (static, on CDN)
  • Queue positions + admissions
  • API gateway: token check
  • Booking service
  • Seats, holds, orders
  • Hold sweeper, every 5 s
  • Payment provider
  • order.confirmed events
  • Ticket delivery
  • arrive at 10:00
  • ZADD / ZRANK
  • batch + signed token
  • admitted?
  • hold seats, pay
  • hold: conditional UPDATE, 10 min
  • release expired holds
  • charge + Idempotency-Key
  • webhook
  • order paid
  • send tickets
Satış açılışı sırasında bir biletleme altyapısı. Uçtaki bekleme odası hayranları kontrollü bir hızla içeri alır; rezervasyon servisi koltukları koşullu bir güncelleme ve bitiş süresiyle tutar, ödemeyi bir idempotency anahtarıyla alır ve siparişleri bir kuyruk üzerinden onaylar.

Nasıl okunur:

  • Hayranlar CDN üzerindeki bekleme odasına gelir; oda onları bekletir ve imzalı token'larla gruplar hâlinde içeri alır.
  • API ağ geçidi ve rezervasyon servisi yalnızca içeri alınan hayranları görür. Redis kuyruk sıralarını ve giriş durumunu saklar.
  • Koltukları Postgres tutar. Koşullu güncelleme tutmaları oluşturur, benzersizlik kısıtı satışları korur.
  • Ödeme sağlayıcısı bir idempotency anahtarıyla çağrılır ve webhook'ları rezervasyon servisine geri döner.
  • Temizleyici fonksiyon süresi dolan tutmaları serbest bırakır; sipariş kuyruğu e-postayla ve uygulama içinde bilet teslimatını besler.
  • Markdown bloğu satış açılışı rakamlarını ve tutma kurallarını listeler.

Rezervasyon sistemlerini yarış koşulları görünecek şekilde çizmek

  • Kapıyı çekirdekten ayrı çizin. Bekleme odası ile rezervasyon servisinin ölçeklenme modelleri ve sahipleri farklıdır.
  • Tutma süresini ve benzersizlik kuralını diyagrama yazın. Tasarımı bu iki bilgi açıklar.
  • Idempotency anahtarını ödeme kenarına koyun. Yeniden denemelerin güvenli olmasının nedeni odur.
  • Sona erme yolunu gösterin. Koltukların geri döndüğü yer, birçok çifte satış hatasının saklandığı yerdir.

Genel yöntem için sistem tasarımı diyagramı örneğine bakın. Sipariş olayları transactional outbox deseniyle, bilet e-postaları ise bildirim sistemi tasarımındakine benzer bir hat üzerinden yayımlanabilir.

Archboard'da oluşturun

  1. Google ya da GitHub ile giriş yapın ve bir diyagram oluşturun.
  2. Hayranlar için C4 Modeli paketinden bir Kişi, bekleme odası için Bulut paketinden bir CDN düğümü ekleyin.
  3. Rezervasyon servisi için Bulut paketinden bir API ağ geçidi ve bir Hesaplama düğümü ekleyin.
  4. Kuyruk durumu için Veri paketinden bir Redis düğümü, koltuklar, tutmalar ve siparişler için bir Postgres düğümü ekleyin.
  5. Ödeme sağlayıcısı için C4 Modeli paketinden bir Harici sistem, tutma temizleyicisi için Bulut paketinden bir Fonksiyon, sipariş olayları için bir Kuyruk ve bilet teslimatı için bir Hesaplama düğümü daha ekleyin.
  6. Kenarları token'lar, tutma kuralları ve idempotency anahtarıyla etiketleyin ve Temel paketinden rakamları içeren bir Markdown bloğu ekleyin.

Örnek 11 diyagram düğümü kullanıyor ve Free planın sınırları içinde kalıyor.

SSS

Biletleme sistemleri çifte rezervasyonu nasıl önler? Koltuk satırında koşullu bir güncellemeyle; böylece bir koltuğu müsaitten tutulmuşa yalnızca tek bir istek taşıyabilir. Son güvence olarak da bilet tablosunda bir benzersizlik kısıtı bulunur. Önbellekler ve kuyruklar yükü azaltır, ama kararı veritabanı verir.

Bir koltuk tutma ne kadar sürmeli? Rahatça ödeme yapacak kadar uzun, terk edilen koltukların hızla geri dönmesini sağlayacak kadar kısa. Beş ila on dakika yaygın bir aralıktır. Ödeme başladığında tutmayı uzatın; böylece sona erme, başarılı bir ödemeyle yarışamaz.

Sanal bekleme odası nedir? Rezervasyon akışının önünde, kullanıcıları sistemin kaldırabileceği bir hızla içeri alan bir kuyruk. Genellikle CDN'de statik bir sayfadır; sıralar erken gelenler için rastgele, sonrasında ise varış sırasına göre atanır.

Koltuk tutmalar Redis'te mi yoksa veritabanında mı durmalı? Doğruluk kaynağı veritabanı olmalıdır, çünkü Redis replikasyonu asenkrondur ve bir failover anahtarları kaybettirebilir. Veritabanı benzersizliği zorlamaya devam ettiği sürece Redis, önde hızlı bir filtre olarak iyi çalışır.

Tutmanın süresi dolduktan sonra başarılı olan bir ödemeyi nasıl ele alırsınız? Ödeme sırasında tutmaları uzatarak bunu önleyin. Yine de olursa otomatik olarak iade edin ya da eşdeğer bir koltuk önerin ve bunu tespit etmek için sağlayıcının webhook'larını kullanın.

Ücretsiz başlayın — tek kullanıcı, kredi kartı gerekmez.

İlgili yazılar

← Blog'a dön

Bir dakikada canvas açın

Google veya GitHub ile giriş yapın. Tek koltuk ücretsiz; ekip katılınca planı yükseltin.