İçeriğe geç
academia.sh

Ders 09 / 25

Mesaj Kuyrukları

Kuyruğun en küçük hâlinin kurulması: yazma, alma, onay ve görünmezlik süresi, onaylanmayan iletinin yeniden teslim edilmesinin ölçülmesi, rekabet eden tüketicilerin tek iletiyi tek tarafa vermesi ve kuyruk derinliğinin üretim ile tüketim hızına bağlı davranışı.

İçindekiler

Önceki ders yan işleri ödünç kaydıyla aynı işlemde giden kutusuna yazdı ve dokuz işin süreç yeniden başladıktan sonra hâlâ orada olduğunu gösterdi. Orada bıraktığımız sorular şunlardı: bu satırları kim okuyacak, iki okuyucu aynı satırı aynı anda alırsa ne olacak, bir okuyucu satırı alıp işlemeden çökerse iş nasıl geri dönecek?

Üçünün de yanıtı tek bir yapıdadır. Mesaj kuyruğu (message queue), bir tarafın yazdığı ve başka bir tarafın okuduğu, okunanın ne zaman silineceğine okuyanın karar verdiği kalıcı bir listedir. Bu ders kuyruğu en küçük hâliyle kurar ve her iletinin kaç kez teslim edildiğini sayar.

Kuyruğun Dört İşlemi

Yazan tarafa üretici (producer), okuyan tarafa tüketici (consumer) denir. Aralarındaki sözleşme dört işlemden oluşur. Üretici yaz ile iletiyi kuyruğa bırakır. Tüketici al ile bir ileti ister. İş bitince onayla ile iletinin silinmesini söyler. Dördüncüsü işlem değil, bir süredir: alınan ileti onaylanana kadar başka tüketiciye verilmez, ama bu koruma sonsuza kadar sürmez — görünmezlik süresi (visibility timeout) dolduğunda ileti yeniden görünür olur.

Aşağıdaki model bu dördünü tutar. Zaman gerçek saatten değil, açıkça ilerletilen bir sanal saatten okunur; böylece ölçümler makineden bağımsız olur.

// kuyruk.mjs — onay ve gorunmezlik suresi olan en kucuk kuyruk modeli
export class Kuyruk {
  constructor(gorunmezlik = 30) {
    this.gorunmezlik = gorunmezlik;   // alinan ileti kac ms boyunca baskasina verilmez
    this.iletiler = [];               // {id, govde, gorunur, teslim}
    this.sonrakiId = 1;
    this.simdi = 0;                   // sanal saat: gercek zamana bagli degil
    this.toplamTeslim = 0;
  }
  yaz(govde) {                        // uretici tarafi
    this.iletiler.push({ id: this.sonrakiId++, govde, gorunur: 0, teslim: 0 });
    return this.iletiler.at(-1).id;
  }
  al() {                              // tuketici tarafi
    const i = this.iletiler.find((m) => m.gorunur <= this.simdi);
    if (i === undefined) return null;
    i.gorunur = this.simdi + this.gorunmezlik;
    i.teslim += 1;
    this.toplamTeslim += 1;
    return { id: i.id, govde: i.govde, teslim: i.teslim };
  }
  onayla(id) {                        // is bitti: ileti kuyruktan silinir
    const once = this.iletiler.length;
    this.iletiler = this.iletiler.filter((m) => m.id !== id);
    return once !== this.iletiler.length;
  }
  ilerlet(ms) { this.simdi += ms; }
  get derinlik() { return this.iletiler.length; }
  get gorunur() { return this.iletiler.filter((m) => m.gorunur <= this.simdi).length; }
}

Beklenen durumda üretici beş iş yazar, tek tüketici hepsini alıp onaylar.

// mutlu-yol.mjs — bes is yazilir, tek tuketici hepsini alip onaylar
import { Kuyruk } from "./kuyruk.mjs";
const k = new Kuyruk();
const ISLER = [
  "gecikme_bildirimi odunc=1", "gecikme_bildirimi odunc=2", "stok_ozeti sube=1",
  "rezervasyon_ilerlet kitap=3", "rapor_satiri odunc=1",
];
for (const is of ISLER) k.yaz(is);
console.log(`uretildi=${ISLER.length}  kuyruk derinligi=${k.derinlik}`);

let islenen = 0;
for (let ileti = k.al(); ileti !== null; ileti = k.al()) {
  islenen += 1;
  k.onayla(ileti.id);
}
console.log(`islenen=${islenen}  toplam teslim=${k.toplamTeslim}  kalan derinlik=${k.derinlik}`);
node mutlu-yol.mjs
uretildi=5  kuyruk derinligi=5
islenen=5  toplam teslim=5  kalan derinlik=0

Beş iş, beş teslim, sıfır kalan. Üretici ile tüketici arasında hiçbir doğrudan çağrı yok: üretici tüketicinin çalışıp çalışmadığını bilmiyor, tüketici işi kimin ürettiğini bilmiyor. Kuyruk ikisini zamanda ayırır — bu, alan olaylarındaki gevşek bağlanmanın üstüne eklenen ikinci bir ayrımdır.

Onay Neden Ayrı Bir Adım

al iletiyi silmez. Silseydi, işi alan tüketicinin çökmesi işin kaybolması demek olurdu. Bunun yerine ileti geçici olarak görünmez yapılır ve silme kararı onaya bırakılır. Aşağıdaki ölçüm bir tüketicinin ikinci işi onaylamadan sonlandığı durumu koşturur.

// onaysiz.mjs — onaylanmayan ileti gorunmezlik suresi dolunca yeniden teslim edilir
import { Kuyruk } from "./kuyruk.mjs";
const k = new Kuyruk(30);
for (const is of ["gecikme_bildirimi odunc=1", "stok_ozeti sube=1", "rapor_satiri odunc=1"])
  k.yaz(is);

// Birinci tuketici: ikinci isi alirken sureci sonlaniyor, onay gondermiyor.
const cokecek = new Set(["stok_ozeti sube=1"]);
for (let ileti = k.al(); ileti !== null; ileti = k.al()) {
  const durum = cokecek.has(ileti.govde) ? "onaysiz kaldi" : "onaylandi";
  if (durum === "onaylandi") k.onayla(ileti.id);
  console.log(`t=${k.simdi} tuketici-1 ileti=${ileti.id} teslim=${ileti.teslim} ${durum}`);
}
console.log(`t=${k.simdi} derinlik=${k.derinlik} gorunur=${k.gorunur}`);

k.ilerlet(30);                       // gorunmezlik suresi doldu
console.log(`t=${k.simdi} derinlik=${k.derinlik} gorunur=${k.gorunur}`);

// Ikinci tuketici ayni isi bulur.
for (let ileti = k.al(); ileti !== null; ileti = k.al()) {
  k.onayla(ileti.id);
  console.log(`t=${k.simdi} tuketici-2 ileti=${ileti.id} teslim=${ileti.teslim} onaylandi`);
}
console.log(`toplam teslim=${k.toplamTeslim}  yazilan ileti=3  kalan derinlik=${k.derinlik}`);
node onaysiz.mjs
t=0 tuketici-1 ileti=1 teslim=1 onaylandi
t=0 tuketici-1 ileti=2 teslim=1 onaysiz kaldi
t=0 tuketici-1 ileti=3 teslim=1 onaylandi
t=0 derinlik=1 gorunur=0
t=30 derinlik=1 gorunur=1
t=30 tuketici-2 ileti=2 teslim=2 onaylandi
toplam teslim=4  yazilan ileti=3  kalan derinlik=0

Üç ileti yazıldı, dört teslim yapıldı. İkinci ileti iki kez teslim edildi çünkü ilk teslim onaysız kaldı. Ölçümün en önemli satırı ortadaki ikisidir: onaysız ileti kuyrukta duruyordu (derinlik=1) ama görünür değildi (gorunur=0); süre dolunca görünür oldu. Görünmezlik süresi, “bu iş şu an başka biri tarafından yapılıyor” varsayımının son kullanma tarihidir.

Sürenin seçimi bir ödünleşmedir. İşin en uzun süresinden kısa seçilirse, iş hâlâ sürerken ileti ikinci bir tüketiciye verilir ve aynı iş iki kez yapılır. Çok uzun seçilirse, gerçekten çöken bir tüketicinin bıraktığı iş uzun süre kimseye ulaşmaz. Uzun süren işler için doğru çözüm süreyi büyütmek değil, tüketicinin çalışırken süreyi uzatmasıdır.

Buradan kuyruğun temel garantisi çıkar: onaylanmayan iş kaybolmaz, ama kaybolmaması yinelenebilmesi pahasınadır. Teslim sayısı iletinin işlenme sayısı değildir; yalnız kuyruğun onu kaç kez verdiğidir.

Rakip Tüketiciler

Kuyruğun ikinci temel özelliği, aynı kuyruktan okuyan birden çok tüketicinin işi paylaşmasıdır. Bu düzene rekabet eden tüketiciler denir: her ileti tam olarak bir tüketiciye gider, çünkü alınan ileti diğerleri için görünmez olur.

// rakip-tuketiciler.mjs — ayni kuyruktan okuyan uc tuketici; her ileti tek tuketiciye gider
import { Kuyruk } from "./kuyruk.mjs";
const k = new Kuyruk();
for (let i = 1; i <= 12; i++) k.yaz(`gecikme_bildirimi odunc=${i}`);

const alan = new Map();                       // ileti -> onu isleyen tuketici
const sayac = { "tuketici-1": 0, "tuketici-2": 0, "tuketici-3": 0 };
const adlar = Object.keys(sayac);
for (let sira = 0; k.derinlik > 0; sira++) {
  const ad = adlar[sira % adlar.length];
  const ileti = k.al();
  if (ileti === null) break;
  alan.set(ileti.id, ad);
  sayac[ad] += 1;
  k.onayla(ileti.id);
}
console.log(`dagitim: ${Object.entries(sayac).map(([a, n]) => `${a}=${n}`).join("  ")}`);
console.log(`farkli ileti sayisi=${alan.size}  toplam teslim=${k.toplamTeslim}  kalan=${k.derinlik}`);
node rakip-tuketiciler.mjs
dagitim: tuketici-1=4  tuketici-2=4  tuketici-3=4
farkli ileti sayisi=12  toplam teslim=12  kalan=0

On iki iş üç tüketiciye dörder dağıldı. Farklı ileti sayısı ile toplam teslim sayısının eşit olması, hiçbir iletinin iki tüketiciye birden gitmediğini söyler. Gecikme bildirimi işi üçe bölünebildiği için tüketici eklemek doğrudan kapasite eklemektir; ödünç verme isteğinin kendisinde böyle bir bölünebilirlik yoktur.

Kuyruk Derinliği

Kuyrukta bekleyen ileti sayısına kuyruk derinliği denir ve tek bir şeyi ölçer: üretim hızı ile tüketim hızının farkını. Aşağıdaki koşum her turda beş iş üretir, her tüketici turda üç iş bitirir.

// derinlik.mjs — uretim hizi tuketim hizini asinca kuyruk derinligi buyur
import { Kuyruk } from "./kuyruk.mjs";

function kosum(tuketiciSayisi, tur = 6, uretim = 5, tuketimHizi = 3) {
  const k = new Kuyruk();
  const derinlikler = [];
  for (let t = 1; t <= tur; t++) {
    for (let i = 0; i < uretim; i++) k.yaz(`is t=${t} n=${i}`);
    for (let i = 0; i < tuketiciSayisi * tuketimHizi; i++) {
      const ileti = k.al();
      if (ileti === null) break;
      k.onayla(ileti.id);
    }
    k.ilerlet(1000);
    derinlikler.push(k.derinlik);
  }
  return derinlikler;
}

for (const n of [1, 2, 3]) {
  const d = kosum(n);
  console.log(`tuketici=${n}  tur sonu derinlikleri: ${d.join(" ")}`);
}
node derinlik.mjs
tuketici=1  tur sonu derinlikleri: 2 4 6 8 10 12
tuketici=2  tur sonu derinlikleri: 0 0 0 0 0 0
tuketici=3  tur sonu derinlikleri: 0 0 0 0 0 0

Tek tüketiciyle derinlik her turda ikişer artıyor; kuyruk işi yutmuyor, yalnız erteliyor. İki tüketici kapasiteyi altıya çıkardığı için derinlik sıfırda kalıyor, üçüncü tüketici hiçbir şey eklemiyor. Kuyruk bir hız düzenleyici değildir; ani yükü düzleştirir, sürekli farkı kapatmaz. Derinliğin zamanla artması tüketici eksikliğinin doğrudan göstergesidir ve izlenmesi gereken ilk ölçüttür.

Kuyruğun Verdiği ve Vermediği

Kuyruk üç şeyi verir: iletinin kalıcılığı, işin bölünebilmesi ve tarafların zamanda ayrılması. Vermedikleri de aynı ölçüde belirleyicidir.

Tek teslim vermez. Ölçümde gördüğümüz gibi onaylanmayan ileti yeniden teslim edilir; işlemenin tekrar altında etkisiz olması tüketicinin sorumluluğudur. Etkisiz İşlemler dersinde kurulan islenen_ileti bekçisi tam bu koşul içindir.

Sıra vermez. Rakip tüketiciler işi paylaştığı anda, iki iletinin bitiş sırası yazılma sırasından farklı olabilir. Aynı kitabın iki olayı iki tüketiciye düşerse ikincisi önce bitebilir.

Yayın vermez. Bir ileti tam olarak bir tüketiciye gider. Aynı olayın hem bildirim hem rapor hem de arama dizini tarafından görülmesi gerekiyorsa kuyruk tek başına yetmez.

Özet

  • Kuyruk dört işlemle tanımlanır: üreticinin yazması, tüketicinin alması, işi bitirince onaylaması ve alınan iletinin görünmezlik süresi boyunca başkasına verilmemesi.
  • Onaysız kalan ileti kuyrukta durur ama görünmez olur; süre dolunca yeniden görünür oldu ve üç yazılan ileti için toplam dört teslim ölçüldü.
  • Görünmezlik süresi işin en uzun süresinden kısa seçilirse aynı iş iki kez yapılır, çok uzun seçilirse çöken tüketicinin işi uzun süre bekler.
  • Rakip tüketicilerde her ileti tek tarafa gider: on iki iş üç tüketiciye dörder dağıldı, farklı ileti sayısı ile toplam teslim sayısı eşit çıktı.
  • Kuyruk derinliği üretim ile tüketim hızının farkını ölçer; tek tüketicide her turda ikişer arttı, ikinci tüketici eklendiğinde sıfırda kaldı, üçüncüsü hiçbir şey değiştirmedi.

Sonraki Adım

Kuyruğun vermediklerinden sonuncusu bir sonraki dersin konusudur. Ödünç verme olayı yalnız bildirim servisini değil, şube stok özetini ve aylık raporu da ilgilendiriyor. Aynı olayı üç tarafa da ulaştırmak için üç ayrı kuyruğa üç kopya yazmak, üreticiyi yeniden tüketicilerin listesine bağlar — tam da kaçınmak istediğimiz şeye. Sonraki ders bu bağı koparan yayımla–abone ol modelini kurar, tek iletinin abone sayısına göre kaç teslime dönüştüğünü sayar ve yavaş bir abonenin diğerlerini etkileyip etkilemediğini ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat