Eksiksiz NATS Geliştirici Rehberi

11 Ağustos 2026 · netologist · 14 dakika, 2885 kelime ·

NATS ile üretim (production) sistemleri kurmak için derinlemesine ve pratik bir kaynak — Core NATS, JetStream, güvenlik, kümeleme (clustering) ve kanıtlanmış tasarım desenlerini (design pattern) kapsar.


İçindekiler

  1. NATS Nedir
  2. Temel Kavramlar
  3. Core NATS Mesajlaşma
  4. Subject’ler ve Wildcard’lar
  5. Queue Group’lar
  6. Request-Reply
  7. JetStream’e Genel Bakış
  8. Stream’ler
  9. Consumer’lar
  10. Key/Value Store
  11. Object Store
  12. Güvenlik
  13. Kümeleme ve Supercluster’lar
  14. İzleme ve Gözlemlenebilirlik
  15. İstemci Kütüphaneleri
  16. Tasarım Desenleri (Patterns)
  17. En İyi Uygulamalar Kontrol Listesi
  18. Sık Yapılan Hatalar
  19. Dağıtım (Deployment) Örnekleri
  20. Kaynaklar

1. NATS Nedir

NATS, Go dilinde yazılmış, basitlik ve hız üzerine tasarlanmış, hafif ve yüksek performanslı bir mesajlaşma sistemidir. Şunları sağlar:

Neden Kafka/RabbitMQ yerine NATS?

ÖzellikNATSKafkaRabbitMQ
Operasyonel karmaşıklıkÇok düşük (tek binary)Yüksek (ZK/KRaft, broker’lar)Orta
Gecikme (latency)Milisaniye altıDüşük-msDüşük-ms
KalıcılıkOpsiyonel (JetStream)Her zamanOpsiyonel
ProtokolBasit metin tabanlıÖzel binaryAMQP
Çoklu kiracılık (multi-tenancy)Account (yerleşik)ACL’lervhost’lar
Edge / IoT uyumluluğuEvet (leaf node, minik ayak izi)HayırKısmi
Exactly-onceEvet (JetStream + dedup)Evet (transaction’lar)Hayır (native olarak)

2. Temel Kavramlar


3. Core NATS Mesajlaşma

Publish / Subscribe

nc, _ := nats.Connect(nats.DefaultURL)
defer nc.Close()

sub, _ := nc.Subscribe("orders.created", func(msg *nats.Msg) {
    fmt.Printf("Alındı: %s\n", string(msg.Data))
})
defer sub.Unsubscribe()

nc.Publish("orders.created", []byte(`{"id": 123}`))

Core NATS en fazla bir kez (at-most-once) teslimat sağlar: dinleyen bir subscriber yoksa mesaj kaybolur. Kalıcılık, onaylama (ack) veya tekrar oynatma (replay) yoktur. Bu bilinçli bir tasarım kararıdır — geçici sinyaller, telemetri, servis keşfi (service discovery) ve düşük gecikmeli RPC için kullanın. Kalıcılık gerektiğinde JetStream kullanın.

Bağlantı Yaşam Döngüsü ve Yeniden Bağlanma

Production ortamında yeniden bağlanma davranışını her zaman açıkça yapılandırın:

nc, err := nats.Connect(
    "nats://server1:4222,nats://server2:4222,nats://server3:4222",
    nats.MaxReconnects(-1),          // sonsuza kadar dene
    nats.ReconnectWait(2*time.Second),
    nats.Timeout(5*time.Second),
    nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
        log.Printf("bağlantı koptu: %v", err)
    }),
    nats.ReconnectHandler(func(nc *nats.Conn) {
        log.Printf("yeniden bağlanıldı: %s", nc.ConnectedUrl())
    }),
    nats.ClosedHandler(func(nc *nats.Conn) {
        log.Printf("bağlantı kapatıldı")
    }),
)

Bağlantı ilk kurulurken client’ın diğer node’ları keşfedebilmesi için her zaman tek bir URL yerine birden fazla seed URL verin.


4. Subject’ler ve Wildcard’lar

Subject’ler nokta ile ayrılmış token’lardır: region.service.event, örn. eu.billing.invoice_created.

Subject Tasarımı için En İyi Uygulamalar

  1. Subject’leri geniş’ten dar’a doğru hiyerarşik tasarlayın: <domain>.<entity>.<event>.<region> — bu, consumer’ların ihtiyaç duydukları herhangi bir granülaritede subscribe olmasına imkan tanır.
  2. Yüksek kardinaliteli dinamik veriyi asla baştaki token olarak koymayın — örn. <user_id>.events yapmayın; events.user.<user_id> tercih edin, böylece events.> üzerindeki wildcard subscription’lar mantıklı kalır ve ACL’ler yönetilebilir olur.
  3. Subject’leri kısa ve öngörülebilir tutun — uzun subject’ler ölçekte eşleştirme (matching) için daha fazla CPU maliyetine yol açar.
  4. Sözleşme (contract) değiştiğinde subject’leri versiyonlayın: mevcut orders.created‘ı sessizce bozmak yerine orders.v2.created kullanın.
  5. Her servis/takım için ayrılmış bir isim alanı (namespace) ayırın, örn. sahibi olan servisle önekleyin: billing.invoice.created.

5. Queue Group’lar

Queue group’lar yük dengelemeli (load-balanced) pub/sub sağlar — aynı queue group’taki birden fazla subscriber’dan yalnızca biri her mesajı alır (round-robin dağıtım), bu da worker’ların yatay ölçeklenmesini sağlar.

nc.QueueSubscribe("work.jobs", "workers", func(msg *nats.Msg) {
    process(msg)
})

Bu worker’ın N adet örneğini aynı queue adıyla çalıştırın — NATS mesajları otomatik olarak aralarında dağıtır. Subject-tabanlı worker havuzları için wildcard’larla birleştirin:

nc.QueueSubscribe("orders.*.process", "order-workers", handler)

Desen: crash durumunda mesaj kaybını tolere edebiliyorsanız (yeniden teslimat yok) Core NATS ölçeklenen worker’lar için queue group kullanın; garanti işleme gerekiyorsa JetStream pull consumer kullanın.


6. Request-Reply

Core NATS yerleşik RPC semantiğine sahiptir: istemci benzersiz bir inbox subject’i üretir, reply-to ile publish eder ve yanıtı bekler.

// Yanıtlayan (Responder)
nc.Subscribe("math.add", func(msg *nats.Msg) {
    result := add(msg.Data)
    nc.Publish(msg.Reply, result)
})

// İstek yapan (Requester)
resp, err := nc.Request("math.add", []byte(`{"a":1,"b":2}`), 2*time.Second)

Arka planda bu, geçici bir inbox subject’i (_INBOX.<uuid>) kullanır — istemci kütüphanesi subscribe/unsubscribe işlemlerini şeffaf şekilde yönetir. Her zaman bir timeout belirleyin; yanıtlayanı olmayan bir istek zaten timeout’a kadar bekleyecektir, o yüzden bunu açık ve kısa tutun (DC-içi çağrılar için genellikle 1-5sn).

Scatter-Gather: bir istek publish edin, SubscribeSync + bir timer kullanarak belirli bir zaman penceresinde birden fazla yanıt toplayın; servis keşfi veya “tüm shard’lara sor” desenleri için kullanışlıdır.


7. JetStream’e Genel Bakış

JetStream bir kalıcılık ve akış (streaming) katmanı ekler. Temel yapı taşları:

js, _ := nc.JetStream()

js.AddStream(&nats.StreamConfig{
    Name:     "ORDERS",
    Subjects: []string{"orders.>"},
    Storage:  nats.FileStorage,
    Retention: nats.LimitsPolicy,
    MaxAge:   7 * 24 * time.Hour,
    Replicas: 3,
})

js.Publish("orders.created", []byte(`{"id":123}`))

8. Stream’ler

Retention (Saklama) Politikaları

PolitikaDavranış
LimitsPolicyLimitlere (yaş/boyut/sayı) ulaşılana kadar mesajları tutar — klasik log retention’ı
InterestPolicyBilinen tüm consumer’lar mesajı onayladığında (ack) mesajı siler — work queue için iyi
WorkQueuePolicyMesaj, herhangi bir consumer onayladığı anda silinir (tek-consumer-semantiği garantisi: sadece bir consumer grubu bağlanabilir)

Önemli Stream Yapılandırma Alanları

&nats.StreamConfig{
    Name:              "EVENTS",
    Subjects:          []string{"events.>"},
    Retention:         nats.LimitsPolicy,
    MaxConsumers:      -1,
    MaxMsgs:           1_000_000,
    MaxBytes:          10 * 1024 * 1024 * 1024, // 10GB
    MaxAge:            30 * 24 * time.Hour,
    MaxMsgSize:        1024 * 1024,
    Storage:           nats.FileStorage,
    Replicas:          3,             // Yüksek erişilebilirlik için RAFT ile çoğaltılır (replicate)
    Discard:           nats.DiscardOld,
    Duplicates:        2 * time.Minute, // dedup penceresi
    AllowRollup:       true,
    DenyDelete:        true,          // uyumluluk (compliance): manuel silme yok
    DenyPurge:         false,
}

Mesaj Tekilleştirme (Exactly-Once Publish)

js.Publish("orders.created", data, nats.MsgId("order-123-v1"))

Aynı Nats-Msg-Id ile bir mesaj Duplicates penceresi içinde tekrar gelirse, JetStream sessizce siler — bu, retry-güvenli producer’lar için ücretsiz idempotent publishing sağlar (temel önem taşır).

Stream Mirroring ve Sourcing

js.AddStream(&nats.StreamConfig{
    Name: "ORDERS_EU_MIRROR",
    Mirror: &nats.StreamSource{Name: "ORDERS_EU"},
})

9. Consumer’lar

Push ve Pull

sub, _ := js.PullSubscribe("orders.>", "order-processor",
    nats.ManualAck(),
    nats.AckWait(30*time.Second),
    nats.MaxDeliver(5),
)

for {
    msgs, _ := sub.Fetch(10, nats.MaxWait(5*time.Second))
    for _, m := range msgs {
        if err := process(m); err != nil {
            m.Nak()          // olumsuz onay (negative ack): yeniden teslim et
            continue
        }
        m.Ack()
    }
}

Durable ve Ephemeral

Ack (Onaylama) Stratejileri

ModAnlamı
AckExplicitİstemci her mesajı ayrı ayrı Ack/Nak etmelidir (varsayılan, en güvenli)
AckAllN mesajını onaylamak, önceki onaylanmamış tüm mesajları da onaylar — daha yüksek verim, daha az granülerlik
AckNoneGönder-ve-unut, yeniden teslimat takibi yok

Ack desenleri:

Temel Consumer Yapılandırması

&nats.ConsumerConfig{
    Durable:       "order-processor",
    AckPolicy:     nats.AckExplicitPolicy,
    AckWait:       30 * time.Second,
    MaxDeliver:    5,
    MaxAckPending: 1000,             // backpressure sınırı
    DeliverPolicy: nats.DeliverAllPolicy, // ya da DeliverNewPolicy / DeliverByStartTimePolicy
    ReplayPolicy:  nats.ReplayInstantPolicy, // ya da ReplayOriginalPolicy (orijinal zamanlamaya sadık kal)
    FilterSubject: "orders.created.*",
}

MaxAckPending, birincil backpressure kontrolünüzdür — aynı anda kaç mesajın beklemede (teslim edilmiş ama onaylanmamış) olabileceğini sınırlar, yavaş consumer’ların bunalmasını önler.


10. Key/Value Store

JetStream tabanlı KV store; yapılandırma (config), feature flag’ler, servis keşfi ve dağıtık kilitler (distributed lock) için idealdir.

kv, _ := js.CreateKeyValue(&nats.KeyValueConfig{
    Bucket:  "config",
    History: 5,
    TTL:     0, // süresiz
})

kv.Put("feature.dark_mode", []byte("true"))
entry, _ := kv.Get("feature.dark_mode")

// Değişiklikleri izle (gerçek zamanlı config yayılımı)
watcher, _ := kv.Watch("feature.>")
for update := range watcher.Updates() {
    if update != nil {
        fmt.Println(update.Key(), string(update.Value()))
    }
}

11. Object Store

Normal mesajlar için çok büyük olan blob’lar (dosyalar, resimler, yedekler) için — büyük objeleri bir stream üzerinden otomatik olarak parçalara (chunk) böler.

os, _ := js.CreateObjectStore(&nats.ObjectStoreConfig{Bucket: "uploads"})
os.PutFile("/local/path/report.pdf")
os.GetFile("report.pdf", "/tmp/report.pdf")

12. Güvenlik

Account’lar ve Çoklu Kiracılık (Multi-Tenancy)

Account’lar sert izolasyon (hard isolation) sağlar: her account kendi subject isim alanına (açıkça export/import edilmediği sürece hiçbir account’lar-arası sızıntı olmaz), kendi JetStream kaynak limitlerine ve bağımsız kimlik doğrulamasına sahiptir. Bu, NATS’in temel çoklu kiracılık yapı taşıdır — account’ları altyapıyı paylaşan ayrı organizasyonlar gibi düşünün.

Merkezi Olmayan Kimlik Doğrulama: NKeys ve JWT’ler

nsc add operator MyOperator
nsc add account MyApp
nsc add user --account MyApp MyUser
nsc edit user MyUser --allow-pub "orders.>" --allow-sub "orders.>,_INBOX.>"

TLS

Herhangi bir production veya çoklu host dağıtımında, istemciler ile sunucular arasında ve cluster/gateway route’ları arasında her zaman TLS etkinleştirin:

tls {
  cert_file: "/etc/nats/server-cert.pem"
  key_file:  "/etc/nats/server-key.pem"
  ca_file:   "/etc/nats/ca.pem"
  verify:    true
}

Yetkilendirme (Authorization) için En İyi Uygulamalar

  1. En az yetki (least-privilege) subject izinleri kullanın — bir servise yalnızca ihtiyaç duyduğu tam subject desenlerinde pub/sub verin, production’da asla toptan > erişimi vermeyin.
  2. Kiracıları (tenant) sadece subject önekleriyle değil, ayrı account’larla izole edin — önekler bir konvansiyondur, account’lar ise zorunlu kılınan (enforced) izolasyondur.
  3. Mümkün olduğunda kimlik bilgilerini kısa ömürlü JWT’ler ile döndürün (rotate); statik uzun ömürlü token’lardan kaçının.
  4. Geniş erişim açmak yerine account’lar-arası kasıtlı, açık iletişim için export/import kullanın.

13. Kümeleme ve Supercluster’lar

JetStream Replikasyonu

RAFT tabanlı replikasyon için stream’lerde Replicas: 3 ayarlayın — quorum sağlam olduğu sürece otomatik lider seçimi (leader election) ve veri kaybı olmadan 1 node arızasını tolere eder. JetStream cluster’larını quorum matematiğini korumak için her zaman tek sayıda replika (1, 3, 5) ile dağıtın.

Boyutlandırma (Sizing) Rehberi


14. İzleme ve Gözlemlenebilirlik

Uyarı (alert) kurulacak temel metrikler:


15. İstemci Kütüphaneleri

Resmi/birinci sınıf istemciler: Go (nats.go), Python (nats.py), Node.js (nats.js), Java, C#/.NET, Rust, Ruby, Elixir (gnat), C.

Genel istemci en iyi uygulamaları:

  1. Süreç (process) başına tek bir bağlantı yeniden kullanın; birçok bağlantı açmak yerine subject’leri onun üzerinden çoğullayın (multiplex).
  2. ErrSlowConsumer‘ı her zaman ele alın — bu, callback’inizin yetişemediği ve mesajların bekleyen (pending) tampondan düşürüldüğü anlamına gelir.
  3. Yüksek hacimli subject’leri işleyen subscription’lar için açık PendingLimits belirleyin.
  4. Yüksek verimli producer’lar için tamamlanma callback’i ile async publishing (js.PublishAsync) kullanın, ama kapanmadan önce js.PublishAsyncComplete() ile takip edin/flush edin.

16. Tasarım Desenleri (Patterns)

Olay Odaklı Mikroservisler (Event-Driven Microservices)

Servisler domain olaylarını iyi isimlendirilmiş subject’lerde (<service>.<entity>.<event>) publish eder; diğer servisler ilgilendikleri olaylara wildcard’lar üzerinden subscribe olur. Producer’ları consumer’lardan tamamen ayrıştırır (decouple).

CQRS + Event Sourcing

LimitsPolicy stream’ini kalıcı olay günlüğü (source of truth) olarak kullanın; stream’i durable bir consumer ile tüketip olayları bir veritabanına/cache’e uygulayarak okuma-modeli (read-model) projeksiyonları oluşturun. Projeksiyonları sıfırdan yeniden inşa etmek için stream’i tekrar oynatın (DeliverPolicy: DeliverAllPolicy).

Work Queue (İş Kuyruğu)

Worker örnekleri arasında paylaşılan bir durable adla WorkQueuePolicy stream’leri + pull consumer’lar kullanın — NATS her mesajın gruptaki tam olarak bir worker’a gitmesini garanti eder, onaylandığında silinir.

Saga / Choreography (Koreografi)

Servisleri olaylar üzerinden zincirleyin: Servis A order.created publish eder → Servis B (queue-group subscriber) ödemeyi işler, payment.completed veya payment.failed publish eder → Servis C buna göre tepki verir. Tüm saga’yı izlemek için header’larda korelasyon ID’leri (correlation ID) kullanın.

Request-Reply Mikroservisleri (Senkron RPC)

Kalıcılığa ihtiyaç duymadığınız düşük gecikmeli dahili RPC çağrıları (kimlik doğrulama kontrolleri, aramalar) için Core NATS request-reply kullanın — cluster içindeki servisler-arası çağrılar için HTTP’den çok daha basit ve hızlıdır.

Fan-Out / Fan-In

Fan-out: bir publisher, birçok bağımsız subscriber (her biri bir kopya alır), düz subscribe ile (queue group değil). Fan-in: bir subject’e birçok publisher, tek bir consumer toplar (aggregate) — telemetri/loglama pipeline’larında yaygındır.

Outbox Pattern

Publishing’in bir DB yazımıyla transaction olarak tutarlı olması gerektiğinde, olayı aynı DB transaction’ı içinde bir “outbox” tablosuna yazın, ardından ayrı bir relay süreci, dedup-güvenli, exactly-once-etkili teslimat için DB satır ID’sini Nats-Msg-Id olarak kullanarak JetStream’e publish eder.

KV Üzerinden Dağıtık Kilitler

Hafif dağıtık kilitleme için compare-and-swap primitifi olarak kv.Create() (anahtar zaten varsa başarısız olur) kullanın; anahtarı silerek veya TTL‘in dolmasına izin vererek serbest bırakın.


17. En İyi Uygulamalar Kontrol Listesi


18. Sık Yapılan Hatalar

  1. Kaybedilmemesi gereken herhangi bir şey için Core NATS kullanmak. Subscriber yoksa mesaj gider. Kalıcılık-kritik veri için her zaman JetStream kullanın.
  2. Ack etmeyi unutmak. Onaylanmamış (unacked) mesajlar sonsuza kadar yeniden teslim edilir (MaxDeliver‘a kadar), consumer’lar idempotency’yi ele almazsa yinelenen işleme döngülerine yol açar.
  3. Kolaylık olsun diye ACL’lerde > kullanmak. Bu bir güvenlik anti-pattern’idir; subject’leri her zaman sıkı şekilde sınırlayın.
  4. MaxAckPending ayarlamamak. Sınırsız bekleyen mesajlar yavaş consumer’ları bunaltıp belleği patlatabilir.
  5. Bağlantı string’inde tek seed URL. Yeniden bağlanma denemesi sırasında o tek node çökmüşse, istemci cluster’ın geri kalanını keşfedemez.
  6. Slow consumer hatalarını görmezden gelmek. Bunlar istemci-tarafı bekleyen tampondan mesajları sessizce düşürür — uyarı değil, kritik alarm olarak ele alın.
  7. Stream’leri aşırı replike etmek. Kritik olmayan veride gereksiz R5 replikasyonu, hiçbir fayda sağlamadan yazma gecikmesini artırır.
  8. Failover’ı test etmemek. Uygulamanızın yeniden bağlanma/yeniden seçimi (re-election) düzgün şekilde ele aldığını doğrulamak için staging ortamında düzenli olarak bir JetStream lider node’unu öldürün.
  9. “Queue group” (Core NATS yük dengeleme) ile “work queue stream” (JetStream kalıcı iş dağıtımı) kavramlarını karıştırmak — benzer sorunları çözerler ama çok farklı kalıcılık garantilerine sahiptirler.

19. Dağıtım (Deployment) Örnekleri

Minimal Tek Node (geliştirme)

nats-server -js -sd /data/jetstream

3 Node’lu Cluster (docker-compose taslağı)

services:
  nats1:
    image: nats:2.10-alpine
    command: "-js -sd /data -cluster_name NATS -cluster nats://0.0.0.0:6222 -routes nats://nats2:6222,nats://nats3:6222"
  nats2:
    image: nats:2.10-alpine
    command: "-js -sd /data -cluster_name NATS -cluster nats://0.0.0.0:6222 -routes nats://nats1:6222,nats://nats3:6222"
  nats3:
    image: nats:2.10-alpine
    command: "-js -sd /data -cluster_name NATS -cluster nats://0.0.0.0:6222 -routes nats://nats1:6222,nats://nats2:6222"

Kubernetes

Stream/Consumer’ları deklaratif olarak CRD’ler şeklinde yönetmek için resmi NATS Helm chart‘ını veya NACK (NATS için Kubernetes controller) kullanın:

apiVersion: jetstream.nats.io/v1beta2
kind: Stream
metadata:
  name: orders
spec:
  name: ORDERS
  subjects: ["orders.>"]
  storage: file
  replicas: 3
  maxAge: "168h"

20. Kaynaklar


Bu rehber NATS Server 2.10+ semantiğini yansıtır. API değişiklikleri için her zaman en güncel resmi dokümantasyonla karşılaştırın.