NetMQ ile Aracısız Mesajlaşma
NetMQ, ZeroMQ mesajlaşma modelini .NET tarafında broker zorunluluğu olmadan sunar. Bu sadelik kuyruk sınırı, socket ownership, geri basınç ve hata semantiği kararlarını uygulamanın üzerine bırakır.
Broker olmaması, kuyruk olmadığı anlamına gelmez.
ZeroMQ ailesini ilk kez kullananlarda en sık gördüğüm zihinsel model hatası bu. Ortada RabbitMQ veya Kafka gibi ayrı bir sunucu olmadığı için mesajın doğrudan socket'ten socket'e aktığı ve bütün hata davranışının TCP'ye bırakıldığı sanılıyor. Oysa NetMQ kendi kuyruk, messaging pattern ve state-machine semantiğine sahip.
.NET tarafında hafif bir messaging katmanı gerektiğinde bu yaklaşım çok güçlü olabilir; fakat basitliğin karşılığında bazı mimari kararlar sizin sorumluluğunuza geçer.
NetMQ ne sağlar?
NetMQ, ZeroMQ yaklaşımının native C# implementasyonudur. Request/Reply, Publish/Subscribe, Push/Pull, Router/Dealer gibi messaging pattern'leri sağlar.
Bu pattern'ler TCP socket kullanımını daha yüksek seviyeli bir protokole dönüştürür.
Örneğin RequestSocket ile bir mesaj gönderip ikinci mesajı ilk yanıt gelmeden gönderemezsiniz. Bu kısıt API tuhaflığı değildir; REQ/REP state machine'inin parçasıdır.
Pattern seçimi bu nedenle class seçimi değil, communication contract seçimidir.
Aracısız tasarımın avantajı
Arada ayrı broker process'i olmadığında dağıtım daha küçüktür. Özellikle aynı host veya kontrollü LAN içinde çalışan bileşenlerde bu ciddi sadelik sağlar.
Ek broker kurulacak servis, izlenecek process, disk/persistence politikası, cluster configuration ve kimlik doğrulama/authorization yüzeyi demektir.
Bunların hiçbirine ihtiyaç olmayan transient mesaj akışında ZeroMQ modeli daha uygun olabilir.
Ancak broker'ın ortadan kalkması broker'ın sağladığı durability, replay ve centralized observability özelliklerini de ortadan kaldırabilir.
Socket ownership kritik
NetMQ socket'leri genel amaçlı iş parçacığı-safe nesne gibi paylaşılmamalıdır. Socket'in hangi iş parçacığı/poller tarafından sahiplenildiği açık olmalıdır.
Bu kuralı ihlal etmek başlangıçta çalışıyor görünen, yük altında nondeterministic davranan kod üretir.
Ben bu tür API'lerde ownership'i constructor seviyesinde değil lifecycle seviyesinde tanımlamayı daha doğru buluyorum:
Worker Thread
-> owns socket
-> reads command queue
-> performs send/receive
-> publishes resultBaşka iş parçacığı doğrudan socket'e dokunmak yerine owner iş parçacığına iş aktarır.
Bu model biraz daha fazla kod gibi görünür fakat concurrency semantiğini açık hale getirir.
High-water mark geri basınç sınırıdır
Mesajlar sonsuza kadar buffer'lanamaz. ZeroMQ/NetMQ tarafında high-water mark kuyruk büyümesini sınırlar.
Bu ayarı yalnız tuning parametresi olarak görmek hatalıdır.
HWM dolduğunda ne olacağı socket türü ve send biçimine bağlıdır. Uygulama blocking, zaman aşımı veya drop davranışını bilmelidir.
Uzun süreli sistemlerde sınırlı kuyruk kullanmayı tercih etmemin nedeni burada da aynı: tüketici yavaşsa problem belleğe gizlenmemelidir.
Kuyruk occupancy observability metriği olmalıdır.
PUB/SUB veri kaybını kabul eder
Publisher, subscriber'ın mesajı gerçekten işlediğini bilmez. Subscriber bağlantıyı kurmadan önce yayımlanan mesajları da kaçırabilir.
Bu davranış telemetri, canlı durum veya yeniden üretilebilir state anlık görüntüyü için kabul edilebilir olabilir.
Finansal işlem veya kalıcı iş emri için aynı pattern güvenli değildir.
Bir mesajın önemli olması pattern'i değiştirebilir: transient observation için PUB/SUB, work distribution için PUSH/PULL, synchronous command için REQ/REP, custom async routing için DEALER/ROUTER daha doğal olabilir.
Pattern isimlerini ezberlemekten daha önemli olan delivery beklentisini tanımlamaktır.
TCP ACK uygulama onayı değildir
Alt katmanda TCP kullanılması mesajın business operation olarak işlendiğini garanti etmez.
TCP karşı tarafın kernel buffer'ına kadar ilerlemiş olabilir. Process mesajı okumadan ölebilir. Process okuyup transaction commit etmeden de ölebilir.
Gerçek "işlendi" garantisi gerekiyorsa application-level acknowledgment ve gerekiyorsa idempotent yeniden deneme tasarlanmalıdır.
Bu nokta yalnız NetMQ'ya özgü değildir; messaging sistemlerinde transport semantics ile business semantics arasındaki sınırdır.
Poller ile event loop
NetMQPoller birden fazla socket ve timer'ı tek event loop içinde yönetmek için kullanışlıdır.
Doğru kullanıldığında her bağlantı için iş parçacığı açma ihtiyacını azaltabilir. Ancak geri çağırım içinde uzun süren CPU veya blocking I/O yapılırsa event loop durur.
Bu yüzden poller geri çağırımı işin kendisini değil, mümkün olduğunca state transition ve dispatch işlemini yapmalıdır.
Ağır iş başka çalışana aktarılacaksa geri dönüş kanalının da sınırlı olması gerekir. Aksi halde "event loop bloklanmasın" diye oluşturulan çalışan pool yeni sınırsız kuyruk üretir.
Failure ve reconnect
Network partition, peer restart veya bağlantı kopması normal durum olarak tasarlanmalıdır.
Uygulama peer yeniden gelince socket'in nasıl toparlanacağını, arada üretilen mesajların ne olacağını, yeniden deneme sırasında duplicate ihtimalini ve process restart sonrası in-memory kuyruk kaybını bilmelidir.
Eğer mesajın kaybı kabul edilmiyorsa aracısız in-memory tasarımın üstüne persistence katmanı eklemek gerekebilir. Bu noktada ayrı broker kullanmak yeniden daha doğru seçenek haline gelebilir.
Nerede kullanırım?
NetMQ'yu en anlamlı bulduğum alan, bileşenlerin sahipliğinin net olduğu ve mesajların çoğunun transient olduğu düşük gecikmeli servislerdir.
Aynı makinedeki process'ler, kontrollü çalışan topolojileri, telemetri, command routing ve edge sistemleri buna örnek olabilir.
Buna karşılık audit log, uzun süreli replay, tüketici group koordinasyonu ve kalıcı event akış gerekiyorsa farklı altyapılar daha doğal olabilir.
Mühendislik kararı "broker hızlı mı yavaş mı?" değildir. İstenen delivery semantics hangi bileşende tutulacak sorusudur.
NetMQ broker'ı kaldırarak sistemi küçültür; sorumluluğu kaldırmaz.
Kaynakça
Aracısız Mesajlaşmada Sorumluluk Sınırı
Aracısız mesajlaşma, merkezi broker hizmetini kaldırırken teslim, kuyruk ve hata davranışını ortadan kaldırmaz; bu sorumlulukların daha büyük bölümü uygulama protokolüne geçer. NetMQ kullanırken socket pattern'i kadar high-water mark, reconnect sırasında bekleyen mesajların kaderi ve işlemenin gerçekten tamamlandığını gösteren acknowledgement sınırı da tasarımın parçasıdır.
Bu nedenle düşük dağıtım karmaşıklığı ile daha fazla uygulama sorumluluğu aynı kararın iki yüzüdür. Kalıcı replay, merkezi tüketici takibi veya broker taraflı saklama temel gereksinimse brokerless tasarım yalnız daha hafif olduğu için seçilmemelidir.