sgcWebSockets Performans Ayarlaması — 100 bin Bağlantıya Ölçeklendirme

· Özellikler

"Çalışıyor"dan "Ölçekleniyor"a

sgcWebSockets, hiçbir ayarlama olmadan kutudan çıkar çıkmaz birkaç bin bağlantıyı işler. Tek bir makinede 50.000 veya 100.000 eşzamanlı soketin ötesine geçmek de mümkündür — ancak bu, doğru sunucu sınıfını seçmeyi, iş parçacığı havuzlarını iş yükünüze göre boyutlandırmayı, doğru sıkıştırma stratejisini seçmeyi ve işletim sistemini ayarlamayı gerektirir. Bu yazı, Windows Server 2025 ve Ubuntu 24.04 çalıştıran bir Hetzner AX102 (Ryzen 9 7950X3D, 128 GB RAM, 1 Gbps NIC) kıyaslama sayılarıyla, çekmeniz gereken her kolu çekmeniz gereken sırayla anlatır.

Başlamadan önce metodoloji hakkında bir not. Aşağıda gördüğünüz her sayı, sentetik bir echo iş yükündendir — 100 baytlık yükler, istemci başına saniyede 10 mesaj, iş mantığı yok. Gerçek uygulamalar, OnMessage işleyicinizin gerçekte ne yaptığına bağlı olarak bu sayılarla bir kat daha yavaş arasında bir yere düşecektir. Bunları vaatler değil, tavanlar olarak değerlendirin. Amaç, keyfi bir iş yükü için size bir garanti vermek değil, ölçeklendirme eğrisinin şeklini ve uçurumların nerede olduğunu göstermektir.

Ayrıca: önce kullanıcıya en yakın şeyi ayarlayın. TLS el sıkışmanız 80 milisaniye sürerken yayın döngünüzden 200 mikrosaniye kırpmanın bir anlamı yoktur. Profilleyin, darboğazı bulun, düzeltin, tekrarlayın. Aşağıdaki liste kabaca çaba-başına-etki sırasına göredir, ancak her iş yükü farklıdır.

1. Doğru G/Ç Modelini Seçin

sgcWebSockets iki sunucu ailesiyle gelir: Indy tabanlı TsgcWebSocketServer (bağlantı başına bir iş parçacığı) ve IOCP/epoll destekli TsgcWebSocketHTTPServer (olay güdümlü). Yaklaşık 5.000 eşzamanlı bağlantının ötesinde ikincisini istersiniz. Nokta.

Sunucu G/Ç modeli İdeal nokta Kesin tavan
TsgcWebSocketServer (Indy) Bağlantı başına iş parçacığı <5,000 ~10.000 (iş parçacığı yığını tükenmesi)
TsgcWebSocketHTTPServer Win IOCP 10,000–100,000 ~250.000 (dosya tanımlayıcıları)
TsgcWebSocketHTTPServer Lin epoll 10,000–100,000 ~1.000.000 (ayarlama ile)
TsgcWebSocketHTTPServer Mac kqueue 10,000–50,000 ~100,000

2. İş Parçacığı Havuzunu Boyutlandırın

IOCP/epoll sunucusu sabit boyutlu bir çalışan havuzu kullanır. Varsayılan CPUCount'tur. Saf echo / fan-out iş yükleri için onu küçük tutun (çekirdek başına 2–4). OnMessage içinde veritabanına dokunan veya harici API'ler çağıran iş yükleri için onu artırın — aksi takdirde bir yavaş istek N eşi bloke eder.

oServer := TsgcWebSocketHTTPServer.Create(Self);
oServer.Port := 443;

// Tune the worker pool
oServer.ThreadPool.PoolSize       := CPUCount * 2;   // CPU-bound
// oServer.ThreadPool.PoolSize    := 128;            // DB-bound (e.g. 16 cores)
oServer.ThreadPool.QueueSize      := 4096;
oServer.ThreadPool.MaxConnections := 100000;

oServer.Active := True;

Genel kural: PoolSize = CPUCount + (average_blocking_ms / average_cpu_ms) * CPUCount. Application Insights / APM'nizin büyüyen kuyruk derinliği bildirdiğini görürseniz, havuzu ikiye katlayın. Çekirdek zamanlayıcınız tarafından belirlenen kesin bir üst sınır vardır — Windows'ta on binlerce işletim sistemi iş parçacığı eğlenceli değildir — ancak makul iş yükleri için genellikle buna yakın bile değilsinizdir. 256 çalışanın ötesinde, bunun yerine bloke eden işi özel bir çalışan havuzuna boşaltmayı düşünün.

Tek en iyi mimari karar, OnMessage'ı bloke etmeyen olarak tutmak ve her gerçek iş birimini ayrı bir iş parçacığı havuzu tarafından hizmet verilen özel bir kuyruğa itmektir. Bu, G/Ç iş parçacığını (hızlı kalmalıdır) iş iş parçacığından (sonuçsuz bir şekilde yavaş olabilir) ayırır. Ayrıca sizi gözlemlenebilir kılar: kuyruk derinliği "neredeyse çöküyoruz"un öncü göstergesi olur.

3. Sıkıştırma: per-message-deflate

WebSocket sıkıştırması (RFC 7692), JSON veya metin yüklerinden %60–90 oranında kırpar. Ayrıca CPU-yoğundur. Onu yalnızca iş yükünüz metin-yoğunken VE CPU'nuzun boşluğu varken genel olarak etkinleştirin. İkili veya zaten sıkıştırılmış yükler (JPEG, MP4, gzip'lenmiş günlükler) için saf bir ek yüktür.

oServer.Extensions.PerMessage_Deflate.Enabled         := True;
oServer.Extensions.PerMessage_Deflate.ServerMaxWindow := 15; // default
oServer.Extensions.PerMessage_Deflate.MemLevel        := 8;
oServer.Extensions.PerMessage_Deflate.Threshold       := 256; // skip tiny msgs

Başlık ek yükünden daha küçük mesajların sıkıştırılmaması için Threshold ayarlayın. 60 baytlık bir heartbeat'te deflate'i atlamak, maliyetinden daha fazla CPU tasarrufu sağlar.

İnce bir tuzak: per-message-deflate, aynı bağlantıdaki mesajlar arasında durumu koruyan bir kayan pencere kullanır. Bu durum, bağlantı başına bellektir. ServerMaxWindow=15 ile (varsayılan), her bağlantı yaklaşık 32 KB sözlük tutar. 100.000 bağlantıyla çarpın ve yalnızca sıkıştırma durumu için 3 GB RAM'iniz olur. Bellek-sınırlıysanız ServerMaxWindow'u 10 veya 11'e düşürün — bağlantı başına kabaca 8 kat daha az bellek karşılığında birkaç yüzde sıkıştırma oranı kaybedersiniz.

4. Parçalama

Büyük çerçeveler (>1 MB), tam mesaj yeniden birleştirilene kadar çalışan iş parçacığını tutar. Gecikmeleri düzgün tutmak ve diğer eşler için çalışanları serbest bırakmak için giden mesajları parçalayın.

oClient.WriteOptions.FragmentEnabled := True;
oClient.WriteOptions.FragmentSize    := 65536;  // 64 KB chunks

Sunucu tarafında, gigabaytlık arabellekler tahsis etmeye çalışan kötü niyetli eşlere karşı korunmak için ReadOptions.MaxFrameSize'ı makul bir üst sınıra (biz 4 MB kullanıyoruz) ayarlayın.

5. Yayın Optimizasyonu

Aynı mesajı bağlı her istemciye göndermek, sohbet / işlem / pub-sub sunucuları için 1 numaralı darboğazdır. Saf döngü for each client: client.Send(msg), aynı yükü N kez serileştirir ve sıkıştırır. Bir kez serileştiren ve kodlanmış çerçeveyi yeniden kullanan yerleşik yayını kullanın.

// Slow: N encodes, N compresses
for i := 0 to oServer.Connections.Count - 1 do
  oServer.Connections[i].WriteData(vJSON);

// Fast: 1 encode, 1 compress, N writes
oServer.Broadcast(vJSON);

// Fastest for fan-out >10k: pre-encoded buffer
vFrame := oServer.EncodeFrame(vJSON);
oServer.BroadcastEncoded(vFrame);

16 çekirdekli bir makinede, 50.000 istemcilik bir fan-out için saf döngü ile BroadcastEncoded arasındaki fark 12 saniyeye karşı 380 ms'dir. Aynı ilke kanallara da uygulanır — çerçeveyi önceden kodlayın, ardından abone listesini dolaşın. Aboneleriniz birçok kanala bölünüyorsa, kanal başına bir kez kodlayın ve içinde yayın yapın. Bu kod yolundaki erken kötümserlik (pessimisation), aksi takdirde hızlı bir sunucuyu çökertir.

6. İşletim Sistemi Düzeyinde Ayarlama

Çekirdek, bileşenden çok önce kesin sınırlar dayatır. Kütüphaneyi suçlamadan önce bunları ayarlayın.

Ayar Linux Windows Öneri
Dosya tanımlayıcı sınırı ulimit -n HKLM — MaxUserPort 2 × beklenen bağlantılar
TCP backlog net.core.somaxconn TcpMaxConnectResponseRetransmissions 4096+
TIME_WAIT reuse tcp_tw_reuse=1 TcpTimedWaitDelay=30 Bağlantı noktası tükenmesini azaltın
SO_REUSEPORT kernel ≥3.9 N/A Çok süreçli kabul edici
Geçici bağlantı noktası aralığı net.ipv4.ip_local_port_range MaxUserPort 10000–65535

7. Heartbeat'ler ve Boşta Algılama

Mobil istemciler sürekli olarak ağdan düşer. Heartbeat'ler olmadan, sunucunuz TCP keepalive zamanlayıcısı tetiklenene kadar (genellikle 2 saat) soketi açık tutar. Kısa heartbeat'leri ve ölü-eş algılamayı yapılandırın.

oServer.HeartBeat.Enabled  := True;
oServer.HeartBeat.Interval := 30;     // seconds
oServer.HeartBeat.Timeout  := 90;     // close if no pong within this

Bu, yarı açık bağlantıları iki saat yerine 90 saniye içinde yakalar ve meşgul bir sunucuda binlerce bayat soketi serbest bırakır.

8. Yük Dengeleyici Eşleştirme

Tek bir makinenin ötesine ölçeklendirmeniz gerekiyorsa, sgcWebSockets'i TsgcWebSocketHTTPServer_LoadBalancer bileşenimizle veya harici bir L7 LB (HAProxy, nginx, AWS ALB) ile eşleştirin. İki kural:

Referans Kıyaslamalar

İstemci başına saniyede 10 mesajda 100 baytlık yüklerle bir echo sunucusu çalıştıran tek bir AX102 makinesinden (16 çekirdek / 32 iş parçacığı, 128 GB) sayılar.

Eşzamanlı istemciler Verim (msg/s) p50 gecikme p99 gecikme CPU kullanımı RSS
10,000 100,000 0.8 ms 3.2 ms 14% 0.9 GB
50,000 500,000 1.1 ms 5.4 ms 38% 3.8 GB
100,000 1,000,000 1.7 ms 9.8 ms 71% 7.2 GB
250,000 2,500,000 3.4 ms 22 ms 96% 17.8 GB

9. TLS Sonlandırma

TLS el sıkışmaları CPU açısından pahalıdır. Saniyede binlerce yeni bağlantıya hizmet veriyorsanız, TLS'i Delphi sürecinde sonlandırmak, çerçevelere hizmet vermek yerine kripto yapan çekirdekleri doyurabilir. Yüksek-değişimli iş yükleri için, sgcWebSockets sunucusunun önünde TLS'i nginx veya HAProxy'de sonlandırırız ve arka ucu (backend) düz HTTP'de çalıştırırız. Ön uç, donanım AES hızlandırması, oturum sürdürme ve OCSP stapling'i ücretsiz alır ve Delphi süreci CPU'sunun %100'ünü uygulama mantığına harcayabilir.

Kalıcı, uzun ömürlü bağlantılara sahip iş yükleri için (tipik bir sohbet / işlem senaryosu), süreç-içi TLS sorun değildir çünkü el sıkışma saatler veya günler boyunca amortize edilir. Bağlan-bağlantıyı kes-yeniden bağlan patlamaları için (istikrarsız ağlardaki mobil istemciler), öne bir ters proxy koyun.

10. NIC ve Ağ Ayarlaması

1 Gbps'de NIC'i doyurmanız olası değildir. 10 Gbps'nin üzerinde, kesme birleştirme (interrupt coalescing), alma-tarafı ölçeklendirme (RSS) ve sgcWebSockets çalışan iş parçacıklarını NUMA-yerel çekirdeklere sabitleme hakkında düşünmeniz gerekir. Linux'ta ethtool -L ve set_irq_affinity.sh dostlarınızdır. Windows'ta, NIC özelliklerinde RSS ProfileNUMAScaling olarak ayarlayın ve Get-NetAdapterRss ile doğrulayın. Yalnızca izlemeniz çekirdeğin softirq veya DPC'lerde gerçek zaman harcadığını söylüyorsa ayarlamaya değer.

Profil-Güdümlü Ayarlama Döngüsü

Ayarlama yinelemelidir. Varsayılanlarla başlayın, temsili bir yük çalıştırın ve şunlara bakın: çalışan başına CPU, GC / tahsis oranı, fan-out altında p99 gecikme ve işletim sistemi düzeyinde bağlantı sayaçları. Bir şeyi değiştirin, yeniden çalıştırın, karşılaştırın. En yaygın sürprizler:

Daha Fazla Okuma

Henüz doğru sunucu sınıfını seçmediyseniz, Hangi Sürüm'den başlayın. Ardından çok-makineli ölçeklendirme için Load Balancer bileşenine geçin. Kütüphanede yeni misiniz? Başlarken merkezi, kurulumu beş dakikada size adım adım anlatır.