WebSockets ile Dağıtık Sistem Mimarisi

## WebSockets ile Gerçek Zamanlı Dağıtık Sistemler WebSocket, tek bir TCP bağlantısı üzerinden çift yönlü, kalıcı bir iletişim kanalı açar. Tek sunuc...

WebSockets ile Dağıtık Sistem Mimarisi

WebSockets ile Gerçek Zamanlı Dağıtık Sistemler

WebSocket, tek bir TCP bağlantısı üzerinden çift yönlü, kalıcı bir iletişim kanalı açar. Tek sunuculu bir uygulamada bu yeterlidir; ancak birden fazla sunucu örneği (instance) çalıştırdığınızda istemciler farklı sunuculara bağlanır ve bir istemciye gönderilen mesajın yalnızca o sunucuya bağlı istemcilere ulaşması sorun olur. Bu rehber, WebSocket bağlantılarını yatay ölçeklenebilir hale getirmenin temel mimarisini ve dikkat edilmesi gereken noktaları ele alır.

Önkoşullar

  • HTTP, TCP ve asenkron programlama hakkında temel bilgi.
  • En az bir sunucu tarafı dilde (Node.js, Python, Go vb.) çalışma deneyimi.
  • Bir mesaj aracısı (message broker) kavramına aşinalık (RabbitMQ, Redis Pub/Sub, NATS vb.).

Kod örnekleri kavramsaldır ve dil/runtime sürümüne bağlı değildir. Kullandığınız kütüphanenin güncel sürümüne göre API adlarını doğrulayın.

Temel Kavramlar

WebSocket el sıkışması (handshake): İstemci normal bir HTTP isteği ile bağlantı başlatır; sunucu Upgrade: websocket başlığını kabul ederse bağlantı WebSocket protokolüne yükseltilir. Bu noktadan sonra iletişim HTTP değil, WebSocket çerçeveleri (frame) üzerinden yürür.

Kalıcı bağlantı: Bağlantı açık kaldığı sürece sunucu istemciye, istemci de sunucuya istediği an veri gönderebilir. Bu, klasik istek-cevap modelinden farklıdır ve sunucu tarafında bağlantı durumunun yönetilmesini gerektirir.

Durum (state) problemi: Her istemci belirli bir sunucu örneğine bağlıdır. Bu bağlılık, dağıtık sistemlerde mesaj yönlendirmesinin merkezî bir sorun haline gelmesine yol açar.

Dağıtık Mimarinin Bileşenleri

Tek bir WebSocket sunucusu şu katmanlardan oluşur:

  1. Bağlantı katmanı: İstemci bağlantılarını kabul eder, kimlik doğrulaması yapar ve bağlantıyı canlı tutar.
  2. Oda/kanal yönetimi: İstemcileri mantıksal gruplara ayırır (örneğin bir sohbet odası veya bir belge düzenleme oturumu).
  3. Mesaj yönlendirme: Gelen mesajı doğru alıcılara iletir.

Birden fazla sunucu örneği olduğunda, örnekler birbirinden habersizdir. Bir örnekte üretilen mesajın diğer örneklerdeki istemcilere ulaşması için örnekler arası bir yayın (broadcast) mekanizması gerekir. Bu mekanizma tipik olarak bir mesaj aracısı üzerinden kurulur.

Yayın Katmanı (Pub/Sub)

Yaygın yaklaşım, her sunucu örneğinin bir aracıya abone olmasıdır:

  • Bir istemci mesaj gönderir.
  • Mesajı alan sunucu örneği, mesajı aracıya yayınlar.
  • Aracıya abone olan tüm örnekler mesajı alır.
  • Her örnek, kendi bağlı istemcilerine mesajı iletir.

Bu modelde örnekler birbirini doğrudan tanımaz; ölçekleme, yeni örnek eklemekle sınırlıdır. Redis Pub/Sub, NATS, RabbitMQ ve Kafka bu amaçla kullanılabilir. Seçim, teslim garantisi ve kalıcılık ihtiyacına göre yapılır: Pub/Sub genelde "en iyi çaba" (at-most-once) teslim sunar; kalıcı kuyruklar farklı garantiler sağlar.

Adım Adım Kurulum Mantığı

1. Bağlantıyı kabul et ve kimlik doğrula

WebSocket el sıkışması HTTP üzerinden başladığı için kimlik doğrulaması bu aşamada yapılabilir. Token'ı sorgu parametresi yerine başlıkta veya ilk mesajda taşımak, token'ın sunucu loglarına ve proxy kayıtlarına sızmasını engeller.

İstemci: GET /ws  (Upgrade: websocket, Authorization: Bearer <token>)
Sunucu: 101 Switching Protocols  → bağlantı açık

Kimlik doğrulaması başarısızsa el sıkışmayı reddedin; açık bırakılan bağlantı kaynak tüketir.

2. Bağlantıyı bir yapıya kaydet

Her bağlantıyı bir kimlik ve abone olduğu kanallarla birlikte bellekte tutun:

connections[connectionId] = { socket, userId, channels: Set }
channelMembers[channel]  = Set<connectionId>

Bu yapı yalnızca o sunucu örneğindeki bağlantıları içerir; global bir görünüm değildir.

3. Aracıya abone ol

Sunucu başlarken yayın kanallarına abone olun. Gelen aracı mesajını, yerel bağlantı tablosuna bakarak ilgili istemcilere iletin.

4. Mesajı yayınla

Bir istemciden gelen mesajı işledikten sonra aracıya yayınlayın. Yayını yapan örnek, kendi aboneliği üzerinden mesajı geri alacağı için yerel iletimi ayrıca yapmanız gerekmez; ancak yinelenen teslimi önlemek için mesaj kimliği (message id) kullanın.

5. Bağlantı kapanışını temizle

Bağlantı kapandığında hem bağlantı tablosundan hem de kanal üyeliklerinden kaydı silin. Aksi halde bellek sızıntısı ve ölü bağlantılara gönderim oluşur.

Yaygın Hatalar

Tek örnek varsayımı: Geliştirme ortamında tek sunucu çalıştığı için yayın katmanı atlanır; üretimde ölçeklenince mesajlar kaybolur. Yayın katmanını baştan tasarlayın.

Yapışkan oturum (sticky session) ile sorunu ertelemek: Yük dengeleyiciyi aynı istemciyi aynı sunucuya yönlendirecek şekilde ayarlamak mesaj kaybını çözmez; yalnızca bağlantı sürekliliğini sağlar. Farklı kanallardaki istemciler yine farklı örneklerde olabilir.

Sınırsız mesaj boyutu: WebSocket çerçevelerinin üst sınırını belirlemeyin demek, tek bir istemcinin sunucu belleğini tüketmesine yol açar. Maksimum mesaj boyutu ve hız sınırı (rate limit) tanımlayın.

Kalp atışı (heartbeat) eksikliği: Yarım kalan TCP bağlantıları "açık" görünür ama veri taşımaz. Periyodik ping/pong ile ölü bağlantıları tespit edin.

Yeniden bağlanma stratejisi olmaması: Ağ kesintisinde istemci yeniden bağlanmalı ve kaçırdığı mesajları alabilmelidir. Bunun için mesaj sıra numarası veya son alınan konum bilgisi saklanır.

Sıralama ve teslim garantisi varsayımı: Aracı ve ağ, mesajları sırasız veya yinelenmiş teslim edebilir. Uygulama katmanında idempotent işleme ve gerekirse sıra numarası doğrulaması yapın.

Güvenlik Etkileri

Yetkilendirme her mesajda: Bağlantı anındaki kimlik doğrulaması yeterli değildir. Bir istemcinin belirli bir kanala mesaj gönderme yetkisi olup olmadığını her mesajda kontrol edin; aksi halde bir kullanıcı başka bir kanala yazabilir.

Origin kontrolü: Tarayıcı kaynaklı bağlantılarda Origin başlığını doğrulayın. Aksi halde kötü niyetli bir sayfa, kullanıcının tarayıcısı üzerinden bağlantı açabilir (siteler arası WebSocket hijacking).

Token sızıntısı: Token'ı URL sorgu parametresinde taşımak, sunucu ve proxy loglarına düşmesine neden olur. Başlık veya ilk mesaj üzerinden iletin.

Girdi doğrulama: WebSocket mesajları da HTTP gövdesi gibi güvenilmez girdidir. Şema doğrulaması yapın ve mesajı işlemeden önce ayrıştırma hatalarını yakalayın.

Kaynak tüketimi: Bağlantı sayısı, mesaj boyutu ve mesaj hızı için sınır koyun. Dağıtık ortamda bir örnekteki aşırı yük, aracı üzerinden diğer örnekleri de etkileyebilir.

TLS: Üretimde wss:// kullanın. Şifrelenmemiş ws:// trafiği ağ üzerinde okunabilir.

Ölçekleme Notları

  • Yatay ölçekleme: Yeni örnekler aracıya abone olduğu sürece sisteme eklenebilir; örnek sayısı istemci dağılımına göre artırılır.
  • Kanalların dağılımı: Çok sayıda kanal ve istemci olduğunda, aracı üzerindeki yayın trafiği darboğaz olabilir. Kanal bazlı filtreleme veya konu (topic) ayrımı ile gereksiz yayını azaltın.
  • Durum dışsallaştırma: Bağlantı durumu bellekte tutulur; örnek çökerse o örnekteki bağlantılar kopar. İstemci yeniden bağlanma ve durum kurtarma mantığı bu yüzden zorunludur.
  • İzleme: Bağlantı sayısı, mesaj gecikmesi, aracı gecikmesi ve hata oranı izlenmelidir. Dağıtık sistemde sorunun hangi katmanda olduğunu ayırt etmek için bu metrikler gereklidir.

Sonuç

WebSocket'i dağıtık hale getirmenin özü, bağlantı durumunun sunucuya özgü olduğunu kabul etmek ve örnekler arası iletişimi bir yayın katmanına devretmektir. Doğru tasarım; kimlik doğrulama ve yetkilendirmeyi her mesajda uygulamak, mesaj boyutu ve hızını sınırlamak, ölü bağlantıları temizlemek ve yeniden bağlanma durumunda kaçırılan mesajları kurtarabilmek üzerine kurulur. Aracı seçimi ve teslim garantisi ihtiyaçlarınıza göre değişir; bu nedenle mimariyi bu garantileri açıkça belirterek tasarlayın.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz