---
title: 'Yayımla–Abone Ol'
source: 'https://academia.sh/tr/kurslar/eszamansiz-isleme/yayimla-abone-ol'
course: 'Önbellekleme, Kuyruklar ve Eşzamansız İşleme'
language: tr
updated: '2026-08-17T18:06:47+00:00'
license: 'CC BY-SA 4.0'
---

# Yayımla–Abone Ol

Tek iletinin birden çok ilgiliye ulaştırılması: konu ile abonelik ayrımı, tek yayımın abone sayısına göre kaç teslime dönüştüğünün sayılması, yavaş abonenin birikiminin diğerlerinden yalıtılması, süzgeçle elenen kopyaların ölçülmesi ve yayımdan sonra kurulan aboneliğin geçmişi görememesi.

Önceki ders kuyruğun vermediklerini üç maddede toplamıştı; sonuncusu yayındı. Bir ileti tam
olarak bir tüketiciye gider, çünkü alınan ileti diğerleri için görünmez olur. Oysa ödünç
verme olayı üç ayrı tarafı ilgilendiriyor: üyeye gecikme bildirimi gönderen taraf, şube stok
özetini yeniden hesaplayan taraf ve aylık rapora satır ekleyen taraf. Aynı olayın üçüne birden
ulaşması gerekiyor.

Bu dersin sorusu, o "üçüne birden"in nereye yazılacağıdır. Yanıt yalnız bir dağıtım
düzeneği değil, aynı zamanda üreticinin kimin dinlediğini bilmemesini sağlayan bir ayrımdır.

## Üreticinin Üç Hedefi Sorunu

En kısa çözüm, üreticinin üç kuyruğa üç kopya yazmasıdır. Ölçülebilir bir bedeli vardır:
üretici artık üç hedefin adını bilir. Dördüncü ilgili taraf — diyelim ki arama dizini —
eklendiğinde ödünç verme kodunun değişmesi gerekir. Beşincisi eklendiğinde yine değişir.
Kuyruk üretici ile tüketiciyi zamanda ayırmıştı, ama üretici hâlâ tüketicilerin **listesini**
taşıyor.

İş Mantığı Yerleşimi konusunda alan olaylarının çözdüğü bağlanma sorunu budur: yayımlayan
taraf dinleyicileri saymaz, yalnız olduğu şeyi bildirir. Yayımla–abone ol (publish–subscribe),
aynı ayrımı süreç sınırının ötesine taşır. Üretici tek bir **konuya** (topic) yazar; kimin
okuyacağını konu bilir.

## Konu ve Abonelik

Ayrımın iki adı vardır ve karıştırılmamaları gerekir. **Konu**, iletilerin yazıldığı adlı
kanaldır. **Abonelik** (subscription), bir ilgili tarafın o konudaki kendi teslim sırasıdır.
Yayımlanan her ileti, yayım anında var olan her aboneliğe birer kopya olarak düşürülür;
abonelikler birbirinin ilerlemesini etkilemez.

Aşağıdaki model bu ikisini tutar. Zaman gerçek saatten değil, her abonelik için açıkça
harcanan bir bütçeden okunur; böylece ölçümler makineden bağımsız kalır.

```js
// konu.mjs — konu ve abonelikler: yayimlanan ileti her abonelige birer kopya dusurur
export class Konu {
  constructor() {
    this.abonelikler = new Map();   // ad -> {kuyruk, sure, suzgec, islenen, elenen}
    this.yayin = 0;                 // yayimlanan ileti sayisi
    this.kopya = 0;                 // aboneliklere dusurulen kopya sayisi
    this.teslim = 0;                // aboneler tarafindan islenen kopya sayisi
  }
  aboneOl(ad, { sure = 1, suzgec = () => true } = {}) {
    this.abonelikler.set(ad, { ad, kuyruk: [], sure, suzgec, islenen: 0, elenen: 0 });
  }
  yayimla(tur, govde) {                       // uretici tarafi: tek cagri
    const ileti = { id: ++this.yayin, tur, govde };
    for (const a of this.abonelikler.values()) {
      if (!a.suzgec(tur)) { a.elenen += 1; continue; }
      a.kuyruk.push(ileti);
      this.kopya += 1;
    }
    return ileti.id;
  }
  ilerlet(butce) {                            // her abonelik kendi hizinda ilerler
    for (const a of this.abonelikler.values()) {
      let kalan = butce;
      while (a.kuyruk.length > 0 && kalan >= a.sure) {
        a.kuyruk.shift(); a.islenen += 1; this.teslim += 1; kalan -= a.sure;
      }
    }
  }
  bekleyen(ad) { return this.abonelikler.get(ad).kuyruk.length; }
  islenen(ad) { return this.abonelikler.get(ad).islenen; }
}
```

Üreticinin kaç çağrı yaptığı ile sistemin kaç teslim yaptığı ayrı iki sayıdır. Aşağıdaki
koşum abone sayısını birden dörde çıkarırken üretici tarafını hiç değiştirmez.

```js
// tek-yayin.mjs — tek yayimla cagrisinin abone sayisina gore kac teslime dondugu
import { Konu } from "./konu.mjs";

for (const aboneler of [["bildirim"], ["bildirim", "stok-ozeti"],
                        ["bildirim", "stok-ozeti", "rapor"],
                        ["bildirim", "stok-ozeti", "rapor", "arama-dizini"]]) {
  const k = new Konu();
  for (const ad of aboneler) k.aboneOl(ad);
  for (const n of [1, 2, 3, 4]) k.yayimla("odunc_verildi", { oduncId: n });
  k.ilerlet(1000);
  console.log(`abone=${aboneler.length}  yayimla cagrisi=${k.yayin}  ` +
              `dusurulen kopya=${k.kopya}  islenen teslim=${k.teslim}`);
}
```

```sh
node tek-yayin.mjs
```

```
abone=1  yayimla cagrisi=4  dusurulen kopya=4  islenen teslim=4
abone=2  yayimla cagrisi=4  dusurulen kopya=8  islenen teslim=8
abone=3  yayimla cagrisi=4  dusurulen kopya=12  islenen teslim=12
abone=4  yayimla cagrisi=4  dusurulen kopya=16  islenen teslim=16
```

Dört satırın hepsinde yayım çağrısı dört; düşürülen kopya sayısı abone sayısıyla çarpılıyor.
Bu çarpım yayımla–abone ol modelinin hem gücü hem maliyetidir. Gücü: üretici dört aboneli
durumda da tek satır yazar, ilgili taraf eklemek üreticiye dokunmaz. Maliyeti: sistemin
taşıdığı iş, yayım sayısı çarpı abone sayısı kadar büyür. Yayma (fan-out) çarpanı, iletiyi
üretmenin değil taşımanın maliyetini belirler.

Aboneliğin **kendi** teslim sırası olması, önceki dersteki rekabet eden tüketici düzenini geçersiz
kılmaz; onunla birleşir. Bir abonelik içinde birden çok işçi çalışabilir ve o aboneliğe düşen
kopyaları aralarında paylaşır. İki düzenin ayrımı tek cümleyle tutulur: abonelikler arasında
**kopyalama**, abonelik içinde **paylaşma** vardır.

## Yavaş Abone Diğerlerini Bekletir mi

Ayrı teslim sırasının asıl sınavı budur. Aylık rapor abonesi diğerlerinden yavaş çalışsın:
her iletiyi işlemesi üç zaman birimi sürsün, diğerlerininki bir. Her tur dört olay yayımlanır
ve her abonelik turda altı zaman birimi harcayabilir.

```js
// yavas-abone.mjs — rapor abonesi digerlerinden uc kat yavas; birikim abonelik basina olculur
import { Konu } from "./konu.mjs";
const k = new Konu();
k.aboneOl("bildirim", { sure: 1 });
k.aboneOl("stok-ozeti", { sure: 1 });
k.aboneOl("rapor", { sure: 3 });

const satir = (...v) => v.map((x) => String(x).padStart(12)).join("");
console.log(satir("tur", "bildirim", "stok-ozeti", "rapor"), "  (islenen/bekleyen)");
for (let tur = 1; tur <= 6; tur++) {
  for (let i = 0; i < 4; i++) k.yayimla("odunc_verildi", { tur, i });
  k.ilerlet(6);                       // her abonelik turda 6 zaman birimi harcayabilir
  const g = (ad) => `${k.islenen(ad)}/${k.bekleyen(ad)}`;
  console.log(satir(tur, g("bildirim"), g("stok-ozeti"), g("rapor")));
}
```

```sh
node yavas-abone.mjs
```

```
         tur    bildirim  stok-ozeti       rapor   (islenen/bekleyen)
           1         4/0         4/0         2/2
           2         8/0         8/0         4/4
           3        12/0        12/0         6/6
           4        16/0        16/0         8/8
           5        20/0        20/0       10/10
           6        24/0        24/0       12/12
```

Altı turun sonunda bildirim ve stok özeti yirmi dörder iletiyi bitirmiş, hiçbiri beklemiyor.
Rapor abonesi on iki iletide kalmış ve on iki ileti biriktirmiş. Birikim yalnız yavaş
aboneliktedir; hızlı olanların ilerlemesi hiç yavaşlamamıştır.

Bu yalıtımı sağlayan şey abonelik başına ayrı sıradır. Üç abone tek bir sırayı paylaşsaydı,
en yavaş olan diğerlerinin önünde durur ve tüm konunun hızını belirlerdi. Ayrı sıralar bunu
önler, ama sorunu yok etmez: yavaş abonenin birikimi bellekte ya da diskte gerçek bir yer
kaplar ve büyümesi sınırsızdır. Önceki dersin kuyruk derinliği ölçütü burada abonelik başına
izlenir; hangi abonenin geride kaldığı toplam sayıya bakarak görülmez.

## Her Abone Her İletiyi İstemez

Konuya yalnız ödünç verme olayı yazılmaz. İade, üye kaydı ve şube eklenmesi de aynı kanaldan
geçer. Bildirim abonesi üye kaydıyla ilgilenmez, rapor abonesi yalnız ödünç verme satırı
yazar. İlgilenmediği iletiyi alıp atmak, aboneliğe düşürülen her kopyanın yazma ve okuma
maliyetini boşa harcamak demektir.

Aboneliğe bir **süzgeç** verilir: iletinin türüne bakarak kopyanın düşürülüp
düşürülmeyeceğine karar eden koşul. Süzgecin yayım tarafında çalışması belirleyicidir; elenen
ileti aboneliğe hiç yazılmaz.

```js
// suzgec.mjs — abonelik yalniz ilgilendigi turleri alir; elenen ileti kopyalanmaz
import { Konu } from "./konu.mjs";
const TURLER = ["odunc_verildi", "iade_alindi", "uye_kaydedildi", "odunc_verildi",
                "sube_eklendi", "iade_alindi", "uye_kaydedildi", "odunc_verildi"];

function kosum(suzgecli) {
  const k = new Konu();
  const s = (kume) => suzgecli ? (t) => kume.has(t) : () => true;
  k.aboneOl("bildirim", { suzgec: s(new Set(["odunc_verildi", "iade_alindi"])) });
  k.aboneOl("stok-ozeti", { suzgec: s(new Set(["odunc_verildi", "iade_alindi", "sube_eklendi"])) });
  k.aboneOl("rapor", { suzgec: s(new Set(["odunc_verildi"])) });
  for (const t of TURLER) k.yayimla(t, {});
  k.ilerlet(1000);
  return k;
}

for (const suzgecli of [false, true]) {
  const k = kosum(suzgecli);
  console.log(`suzgec=${suzgecli ? "var" : "yok"}  yayin=${k.yayin}  dusurulen kopya=${k.kopya}  ` +
              `bildirim=${k.islenen("bildirim")} stok-ozeti=${k.islenen("stok-ozeti")} ` +
              `rapor=${k.islenen("rapor")}`);
}
```

```sh
node suzgec.mjs
```

```
suzgec=yok  yayin=8  dusurulen kopya=24  bildirim=8 stok-ozeti=8 rapor=8
suzgec=var  yayin=8  dusurulen kopya=14  bildirim=5 stok-ozeti=6 rapor=3
```

Sekiz yayım, süzgeçsiz durumda yirmi dört kopya üretiyor; süzgeçle on dört. Rapor abonesi
sekiz yerine üç ileti işliyor ve atacağı beş iletiyi hiç görmüyor. Süzgecin bir de gizli
faydası vardır: aboneliğin ne istediği kodun içinde değil, aboneliğin tanımında durur.
Bildirim abonesinin hangi olaylarla ilgilendiği, tüketici kodunu okumadan görülür.

Süzgecin sınırı, karar verebilmek için iletinin **başlığında** ne olduğuna bağlıdır.
Türe bakan bir süzgeç ucuzdur. Gövdenin içindeki bir alana bakan süzgeç — "yalnız Merkez
şubesinin ödünçleri" — iletinin çözülmesini gerektirir ve maliyeti abonelik sayısıyla çarpılır.
Bu yüzden ayrım ölçütü genelde iletinin gövdesine değil, yayım anında verilen türe ya da
konuya konur.

## Yayımla–Abone Ol'un Sınırı: Geçmiş Yoktur

Modelin en belirleyici özelliği ölçümde açığa çıkar. Kopya, **yayım anında** var olan
aboneliklere düşürülür. O anda olmayan bir abonelik için ileti hiç yazılmamıştır.

```js
// gec-abone.mjs — abonelik yayimdan sonra kurulursa onceki iletiler o abonelige hic dusmez
import { Konu } from "./konu.mjs";
const k = new Konu();
k.aboneOl("bildirim");
k.aboneOl("stok-ozeti");

for (const n of [1, 2, 3]) k.yayimla("odunc_verildi", { oduncId: n });
k.ilerlet(1000);
console.log(`uc yayin sonra : bildirim=${k.islenen("bildirim")} stok-ozeti=${k.islenen("stok-ozeti")}`);

k.aboneOl("arama-dizini");            // yeni ilgili taraf sonradan baglandi
for (const n of [4, 5]) k.yayimla("odunc_verildi", { oduncId: n });
k.ilerlet(1000);
console.log(`bes yayin sonra: bildirim=${k.islenen("bildirim")} stok-ozeti=${k.islenen("stok-ozeti")} ` +
            `arama-dizini=${k.islenen("arama-dizini")}`);
console.log(`toplam yayin=${k.yayin}  arama-dizininin goremedigi=${k.yayin - k.islenen("arama-dizini")}`);
```

```sh
node gec-abone.mjs
```

```
uc yayin sonra : bildirim=3 stok-ozeti=3
bes yayin sonra: bildirim=5 stok-ozeti=5 arama-dizini=2
toplam yayin=5  arama-dizininin goremedigi=3
```

Arama dizini beş ödünç olayının ikisini gördü. Kaçırdığı üçü bir yerde bekliyor değil; o
kopyalar hiç var olmadı. Aynı boşluk ikinci bir biçimde de ortaya çıkar: işlenmiş bir kopya
aboneliğin sırasından silinir, dolayısıyla arama dizini bir hata yüzünden kendi verisini
kaybederse gördüğü iletileri de yeniden okuyamaz.

Bu, modelin kusuru değil tanımıdır. Yayımla–abone ol iletiyi **dağıtır**, saklamaz. Saklamak
isteyen abone, aldığını kendi tarafında biriktirmek zorundadır. Yeni bir ilgili tarafın
geçmişe bakarak kendini kurabilmesi ya da bir abonenin geriye dönüp yeniden okuyabilmesi
gerekiyorsa, ihtiyaç duyulan şey dağıtım değil, **kalıcı bir kayıttır**.

## Özet

- Konu iletilerin yazıldığı adlı kanaldır; abonelik bir ilgili tarafın o konudaki kendi
  teslim sırasıdır. Abonelikler arasında kopyalama, abonelik içinde paylaşma olur.
- Dört yayım çağrısı, abone sayısı birden dörde çıktığında dört yerine on altı kopyaya
  dönüştü; üretici kodu hiç değişmedi. Yayma çarpanı, taşıma maliyetini belirler.
- Ayrı teslim sıraları yavaş aboneyi yalıtır: altı turun sonunda rapor abonesi on iki ileti
  biriktirirken bildirim ve stok özeti yirmi dörder iletiyi bitirmiş ve sıfır bekleyenle
  kalmıştı.
- Süzgeç yayım tarafında çalışır: sekiz yayım süzgeçsiz yirmi dört kopya üretirken süzgeçle
  on dört kopya üretti, rapor abonesi ilgilenmediği beş iletiyi hiç görmedi.
- Kopya yalnız yayım anında var olan aboneliklere düşürülür; sonradan bağlanan arama dizini
  beş olayın üçünü hiç göremedi. Model iletiyi dağıtır, saklamaz.

## Sonraki Adım

Kuyruk da konu da aynı varsayımı paylaşıyor: teslim edilmiş ileti tüketilmiştir, tüketilmiş
ileti silinir. Bu varsayımın bedeli son ölçümde göründü — sonradan bağlanan taraf geçmişi
göremiyor, hata sonrası hiçbir tüketici geriye dönemiyor. Sonraki ders, tüketmeyi silmekten
ayıran ikinci taşıma modelini kurar: iletilerin sona eklendiği ve okunduğunda yerinde kaldığı
kalıcı sıralı bir günlük. Aynı iş yükü hem kuyrukta hem günlükte koşturulur; yeniden okumanın
neden birinde olanaksız, ötekinde bir konum değerini geri almak kadar ucuz olduğu sayıyla
gösterilir.
