Kurumsal yazılım geliştirmede yıllarını geçirmiş bir ekibin karşılaştığı problemler, ilk bakışta birbirinden çok uzak alanlarda tekrar eder. Gümrük beyanname sistemlerinde eşzamanlı işlem sıkışması, IoT filolarında binlerce cihazdan gelen telemetrinin sıralı işlenmesi, RPA süreçlerinde idempotent adım tasarımı — hepsinin altında aynı üç soru yatar: durumu nerede tutuyoruz, kim otorite, ve sistem yük altında nasıl davranıyor?
Bu yazıda yüksek eşzamanlılık altında çalışan gerçek zamanlı sistemlerin mimari kararlarını ele alacağız. Önce kurumsal dünyadan tanıdık örüntülerle temeli kuracak, ardından bu örüntülerin en zorlayıcı uygulama alanlarından birine — saniye altı gecikmeyle on binlerce istemciye aynı durumu yayınlayan gerçek zamanlı oyun platformlarına — bakacağız.
Yüksek Eşzamanlılık: Durum Nerede Yaşar?
Ölçeklenebilirlik tartışmalarının çoğu, aslında durum yönetimi tartışmasıdır. Durumsuz (stateless) bir HTTP servisini yatayda çoğaltmak kolaydır: yük dengeleyicinin arkasına bir örnek daha eklersiniz, iş biter. Zorluk, durumun uygulama katmanında yaşaması gerektiğinde başlar.
Pratikte üç yaklaşım kullanılır. Paylaşılan durum modelinde tüm örnekler ortak bir veri deposuna (Redis, dağıtık cache, veritabanı) yazar; ölçeklemesi basittir ama her okuma bir ağ turu maliyeti getirir ve depo tek darboğaz hâline gelebilir. Yapışkan oturum modelinde istemci hep aynı örneğe yönlendirilir; gecikme düşer ama örnek düştüğünde durum kaybolur ve yeniden dengeleme zorlaşır. Bölümlenmiş (sharded) durum modelinde ise her mantıksal birim — bir masa, bir oturum, bir cihaz grubu — tam olarak tek bir düğüme atanır ve o düğüm o birimin tek otoritesi olur. Aktör modelinin özü budur ve gerçek zamanlı sistemlerde en çok tercih edilen yaklaşımdır.
Gecikme Bütçesi ve Kuyruk Uçları
Gerçek zamanlı sistemlerde performansı ortalama gecikmeyle ölçmek yanıltıcıdır. Anlamlı olan, p95 ve p99 gibi kuyruk değerleridir. Ortalaması 20 milisaniye olan ama p99'u 800 milisaniyeye çıkan bir sistem, kullanıcıların yüzde birinde tutarsız bir deneyim üretir; senkron bir olayın herkese aynı anda ulaşması gereken bir senaryoda bu kabul edilemez.
Kuyruk gecikmesinin başlıca kaynakları bellidir: çöp toplayıcı duraklamaları, senkron disk yazımları, bağlantı havuzu tükenmesi ve gereksiz serileştirme. Bir gecikme bütçesi tasarlarken bu kalemler ayrı ayrı ölçülür — ağ turu, işleme süresi, kuyrukta bekleme ve istemci render süresi olarak.
Geri Basınç ve Zarif Bozulma
Yük altındaki bir sistemin en tehlikeli davranışı, sınırsız kuyruk biriktirmesidir. Bellek dolana kadar kabul edilen istekler, sonunda tüm düğümü çökertir. Doğru tasarım geri basınç (backpressure) uygular: kuyruk sınırlıdır, dolduğunda üretici yavaşlatılır veya istek reddedilir. Bunun yanına devre kesici (circuit breaker) örüntüsü eklenir; bağımlı bir servis yanıt vermediğinde ona istek göndermeyi durdurup önceden tanımlı bir yedek davranışa geçilir. Amaç, bir bileşenin arızasının tüm sisteme yayılmasını engellemektir.
Gerçek Zamanlı Yayın Mimarisi: WebSocket Katmanı
Sunucudan istemciye sürekli veri akışı gereken senaryolarda HTTP istek-yanıt döngüsü yetersiz kalır. Yoklama (polling) hem gereksiz yük üretir hem gecikme yaratır. Çözüm, tek bir TCP bağlantısı üzerinde çift yönlü ve kalıcı iletişim kuran WebSocket protokolüdür. Bağlantı, standart bir HTTP isteğiyle başlar ve Upgrade başlığıyla protokol yükseltmesi yapılarak kalıcı hâle gelir.
Bu katmanın mimarisinde birkaç karar kritiktir. Bağlantı sayısı arttıkça darboğaz CPU'dan çok dosya tanıtıcı limitleri ve bellek olur; her açık bağlantı için tampon ayrılır. Bu yüzden yayın (broadcast) işlemi her istemci için ayrı ayrı serileştirilmez — mesaj bir kez serileştirilip aynı bayt dizisi tüm abonelere yazılır. Yatay ölçeklemede ise düğümler arası mesaj dağıtımı için bir yayın omurgası (pub/sub) kullanılır: olay tek bir otorite düğümde üretilir, omurga üzerinden tüm ağ geçidi düğümlerine iletilir, her düğüm kendi bağlı istemcilerine yazar.
Crash Tipi Oyunların Mimarisi: Sunucu Otoritesi ve Doğrulanabilir Rastgelelik
Bu mimari kararların en yoğun sınandığı örneklerden biri, crash veya Aviator tipi olarak bilinen gerçek zamanlı oyunlardır. Yapı şudur: bir tur başlar, bir çarpan değeri 1.00'den itibaren artmaya başlar ve önceden belirlenmiş bir noktada durur. Oyuncu, durmadan önce çıkarsa o anki çarpanı alır. Yüzlerce ya da binlerce istemcinin aynı çarpanı milisaniye farkıyla görmesi gerekir.
Mimarinin ilk kuralı sunucu tarafı otoritedir. İstemci hiçbir zaman oyun durumunun kaynağı değildir; yalnızca bir görüntüleyicidir. Sunucu tur başlangıcında bir referans zaman damgası ve tur kimliği yayınlar, çarpan ise deterministik bir fonksiyonla — geçen süreye bağlı üstel bir eğri — hesaplanır. İstemci bu eğriyi kendi tarafında yeniden çizerek akıcı animasyon üretir, ama gerçek değer daima sunucudakidir. Böylece istemci tarafındaki müdahaleler sonucu etkileyemez.
İkinci kural, çıkış (cashout) işleminin sunucuda zaman damgalanmasıdır. İstemcinin gönderdiği "şu çarpanda çıktım" bilgisi kabul edilmez; sunucu, isteğin kendisine ulaştığı ana karşılık gelen çarpanı hesaplar. Bu, ağ gecikmesi eşitsizliğinin istismarını engeller. Aynı oyuncudan gelen mükerrer istekler için idempotans anahtarı kullanılır — kurumsal ödeme sistemlerindeki çift işlem korumasının birebir aynısı.
Provably Fair: Hash Zinciri ve Tohumlama
Bu oyunların ayırt edici teknik bileşeni, sonucun turdan önce belirlendiğinin ve sonradan değiştirilmediğinin kriptografik olarak kanıtlanabilmesidir. Yaygın kurgu şöyle işler:
- Sunucu rastgele bir başlangıç değeri (server seed) üretir ve buna tekrarlı özet fonksiyonu uygulayarak uzun bir hash zinciri oluşturur. Zincirin son halkası kamuya duyurulur.
- Turlar zincirin tersinden tüketilir: her turda bir önceki halka açıklanır ve bunun özeti alındığında duyurulmuş halkayı verdiği herkesçe doğrulanabilir. Özet fonksiyonunun tek yönlü olması sayesinde sunucu zinciri sonradan değiştiremez.
- Turun sonucunu tek başına sunucunun belirlemediğini göstermek için bir de istemci tohumu (client seed) ya da kamuya açık, önceden bilinmeyen bir değer — örneğin gelecekteki bir blok özeti — karıştırılır.
- Sunucu tohumu ile istemci tohumu birleştirilip HMAC gibi anahtarlı bir özet fonksiyonundan geçirilir; çıkan onaltılık değerin bir bölümü sayıya çevrilir ve tanımlı dağıtım fonksiyonuyla çarpan değerine dönüştürülür.
Buradaki dağıtım fonksiyonu, oyunun RTP (Return to Player) değerini belirleyen yerdir. Çarpan dağılımına eklenen küçük bir ev payı, yani house edge, sistemin uzun vadede matematiksel olarak işletmeci lehine çalışmasını sağlar. Provably fair mekanizması bu payı ortadan kaldırmaz — kaldırdığı şey, tekil turların manipüle edilip edilmediği belirsizliğidir. Doğrulanabilirlik ile beklenen değerin pozitif olması iki ayrı meseledir ve bunu karıştırmamak gerekir. Her tur istatistiksel olarak bağımsızdır; geçmiş turların çarpan geçmişi, gelecekteki bir tur hakkında hiçbir bilgi taşımaz.
Yük Dağıtımı ve Dayanıklılık
Ölçeklemede tur mantığı ile bağlantı yönetimi ayrıştırılır. Tur motoru tek otorite olarak çalışır ve sonuçları olay akışına yazar; ağ geçidi düğümleri yalnızca bağlantı tutar ve yayın yapar. Bu ayrım, bağlantı katmanının bağımsız ölçeklenmesini sağlar. Bahis ve çıkış işlemleri ise bakiye tutarlılığı için işlemsel bir depoda, atomik güncellemelerle yürütülür; oyun akışının hızlı yolundan ayrı tutulur. Otorite düğümünün arızasına karşı olay günlüğü kalıcı yazılır, böylece yeni düğüm son tutarlı durumdan devralabilir.
Sonuç
Gerçek zamanlı oyun platformları, kurumsal yazılım mühendisliğinin klasik problemlerini uç noktada test eden bir vitrin sunuyor: bölümlenmiş durum, sunucu otoritesi, idempotans, geri basınç, kriptografik doğrulanabilirlik. Aynı örüntüler bir lojistik takip platformunda, bir IoT telemetri hattında ya da bir RPA orkestratöründe farklı isimlerle karşımıza çıkıyor. Mimariyi öğrenmenin en verimli yolu da bu: problemi en sert biçimde çözmek zorunda kalmış sistemlere bakmak ve oradaki kararı kendi bağlamına taşımak.
18+ · Bu içerik bilgilendirme amaçlıdır. Şans oyunları risk içerir; sorumlu oyun ilkelerine uyunuz.
