İçeriğe geç
academia.sh

Ders 10 / 16

Süreç Yöneticileri

Uygulamanın altındaki katmanın iki ayarı: çöken sürecin anında, geri çekilmeli ve sınırlı denemeyle yeniden başlatılmasının düşen istek sayısına etkisi, hiç başlamayan süreçte başlatma döngüsünün politikaya göre kaç kez döndüğü, bellek sınırının aşılmasında sürecin öldürülmesi ile kayıt atıp ayakta kalması arasındaki fark ve durdurma işaretinden sonra tanınan bekleme süresinin boşaltılan istek sayısını nasıl belirlediği.

İçindekiler

Önceki ders uygulamanın önündeki katmanı yapılandırdı ve sekiz koşumun hepsinde tek bir şeyi varsaydı: uygulama süreci ayaktaydı. Elle başlatıldı, işi bitince elle durduruldu, arada hiç çökmedi.

Üretimde süreci başlatan, çöktüğünde yeniden başlatan, ne kadar bellek kullanabileceğine karar veren ve durdurulacağı zaman ona haber veren başka bir bileşen vardır: süreç yöneticisi. Vekilin ayarları isteğin içeriğini değiştiriyordu; buranın ayarları isteğin yanıtlanıp yanıtlanmayacağını değiştirir.

İki Ayar, Bir İşçi

Süreç yöneticisinin ölçülecek iki ayarı var. Birincisi yeniden başlatma politikası: süreç çıktığında ne kadar beklenip yeniden başlatılacağı ve kaç deneme sonra vazgeçileceği. İkincisi kaynak sınırı: sürecin ne kadar bellek tutabileceği ve sınır aşıldığında ne yapılacağı. Üçüncü bir ayar ikisinin dışında durur ve ikisinden de sessizdir: durdurma işaretinden sonra sürece tanınan bekleme süresi.

  • DP1. İşçi her açık ödünç kaydı için 2 MB ayırır ve tuttuğu toplamı yöneticiye bildirir; sınır bu bildirim üzerinden uygulanır. Gerçek dağıtımda aynı değer işletim sisteminden okunur.
  • DP2. Bir isteğin işi sabit sürelidir. Bellek ölçümünde yanıtlar kabul sırasına göre 100 milisaniye arayla verilir; sınırın hangi anda aşıldığı ve o anda kaç isteğin uçuşta olduğu böylece belirli olur.
  • DP3. Bu derste tek bir işçi süreci vardır. Kopya sayısı sonraki dersin konusudur.

İşçi, ödünç uygulamasının çalışan hâlidir ve dört davranışı bir mod dizgesinden alır.

// topoloji/isci.mjs — odunc isci sureci. Mod dizgesi davranisini belirler:
//   cok=<n>  n istek yanitladiktan sonra cikar   |  baslarken-cok=1  dinlemeden once cikar
//   is=<ms>  bir istegin suresi   basamak=<ms>  kabul sirasina gore isin uzamasi (yanitlar ayrisir)
//   bellek=yumusak  sinirda en eski kayitlarin yarisini atar ve ayakta kalir
//   kapanis=zarif|ani  durdurma isaretinde bekleyen istekleri bosaltir ya da hemen cikar
import { createServer } from "node:http";

const [nokta, modMetni] = [Number(process.argv[2]), process.argv[3] ?? ""];
const M = Object.fromEntries(modMetni.split(",").filter(Boolean).map((p) => p.split("=")));
const KAYIT_MB = 2;                       // tutulan odunc kaydi basina bellek
const SINIR_MB = 40;                      // yumusak sinir esigi
const tutulan = [];
let yanit = 0, atilan = 0, kabul = 0, kapaniyor = false;

if (Number.isInteger(nokta) === false) { console.log("kullanim: node topoloji/isci.mjs <nokta> <mod>"); process.exit(0); }
if (M["baslarken-cok"] === "1") process.exit(9);

const sunucu = createServer((istek, cevap) => {
  cevap.sendDate = false;
  if (kapaniyor) { cevap.writeHead(503).end("kapaniyor"); return; }
  const sira = kabul++;
  tutulan.push(new Array((KAYIT_MB * 1024 * 1024) / 8).fill(1));   // acik odunc kaydi bellekte durur
  if (M.bellek === "yumusak" && tutulan.length * KAYIT_MB > SINIR_MB) {
    atilan += tutulan.splice(0, tutulan.length >> 1).length;       // en eski yarisi atilir, surec yasar
  }
  setTimeout(() => {
    yanit += 1;
    cevap.writeHead(200, { "x-tutulan-mb": String(tutulan.length * KAYIT_MB) }).end("odunc");
    if (process.send !== undefined) process.send({ tutulanMB: tutulan.length * KAYIT_MB, yanit, atilan });
    if (M.cok !== undefined && yanit >= Number(M.cok)) process.exit(9);
  }, Number(M.is ?? 40) + sira * Number(M.basamak ?? 0));
});

process.on("SIGTERM", () => {
  if (M.kapanis === "zarif") { kapaniyor = true; sunucu.close(() => process.exit(0)); }
  else process.exit(0);                    // bekleyen istekler yaniti gormeden dusuyor
});
sunucu.listen(nokta, "127.0.0.1");

Yönetici işçiyi başlatır, bildirimlerini dinler, çıkışını yakalar ve politikayı uygular.

// topoloji/yonetici.mjs — surec yoneticisi. Ayar dizgesi:
//   politika=aninda|geri-cekilmeli|sinirli:<n>   bellek=yok|sert:<mb>
//   sure=<ms>  0 ise durdurma isaretini bekler ve ozet basar; >0 ise o anda zarif durdurmayi baslatir
//   bekleme=<ms>  durdurma isaretinden sonra sert oldurmeye kadar taninan sure
import { fork } from "node:child_process";

const [nokta, ayarMetni, isciMod, etiket] = [process.argv[2], process.argv[3] ?? "", process.argv[4] ?? "", process.argv[5] ?? "-"];
const A = Object.fromEntries(ayarMetni.split(",").filter(Boolean).map((p) => p.split("=")));
const GERI = [100, 200, 400, 800, 1600];          // geri cekilme merdiveni (ms)
let deneme = 0, gecikme = 0, oldurme = 0, tepe = 0, atilan = 0, vazgecti = false, duruyor = false, cocuk = null;

function baslat() {
  let olduruldu = false;
  cocuk = fork("topoloji/isci.mjs", [nokta, isciMod], { stdio: ["ignore", "ignore", "ignore", "ipc"] });
  cocuk.on("message", (m) => {
    tepe = Math.max(tepe, m.tutulanMB);
    atilan = m.atilan;
    const sert = (A.bellek ?? "yok").startsWith("sert:") ? Number(A.bellek.split(":")[1]) : Infinity;
    if (m.tutulanMB > sert && olduruldu === false) {                   // sinir asildi: surec olduruluyor
      olduruldu = true;
      oldurme += 1;
      cocuk.kill("SIGKILL");
    }
  });
  cocuk.on("exit", () => { if (duruyor === false) yeniden(); });
}

function yeniden() {
  const en = (A.politika ?? "").startsWith("sinirli:") ? Number(A.politika.split(":")[1]) : Infinity;
  if (deneme >= en) { vazgecti = true; return; }
  const g = A.politika === "geri-cekilmeli" ? GERI[Math.min(deneme, GERI.length - 1)] : 0;
  deneme += 1;
  gecikme += g;
  setTimeout(baslat, g);
}

if (nokta === undefined) console.log("kullanim: node topoloji/yonetici.mjs <nokta> <ayar> <isci-mod> <etiket>");
else {
  setInterval(() => {}, 60000);          // vazgecildikten sonra da ozet icin ayakta kalinir
  baslat();
  process.on("SIGTERM", () => {
    duruyor = true;
    cocuk?.kill("SIGKILL");
    console.log(`${etiket} yonetici: ${deneme} yeniden baslatma, ${gecikme} ms gecikme, ${oldurme} oldurme, ` +
      `tepe ${tepe} MB, ${atilan} kayit atildi${vazgecti ? ", vazgecti" : ""}`);
    process.exit(0);
  });
  if (Number(A.sure ?? 0) > 0) setTimeout(() => {
    duruyor = true;
    cocuk.kill("SIGTERM");                                            // once zarif kapanma istegi
    const sert = setTimeout(() => cocuk.kill("SIGKILL"), Number(A.bekleme ?? 500));
    cocuk.on("exit", () => { clearTimeout(sert); process.exit(0); }); // bekleme dolmadan cikarsa temiz
  }, Number(A.sure));
}

Yük üreteci istekleri sabit aralıkla ya da hepsini birden gönderir ve üç sonucu ayırır: yanıtlanan, temiz reddedilen, düşen.

// topoloji/yuk.mjs — yuk ureteci. Kip "aralikli": adet istek, aralik ms arayla.
// Kip "esz": adet istek ayni anda. Baglanti kurulamayan ya da yanitsiz kapanan istek "dusen" sayilir.
import { request } from "node:http";

const [nokta, kip, adet, aralik, etiket] = [Number(process.argv[2]), process.argv[3], Number(process.argv[4]), Number(process.argv[5]), process.argv[6] ?? "-"];
const bekle = (ms) => new Promise((c) => setTimeout(c, ms));
let yanitlanan = 0, reddedilen = 0, dusen = 0;

const bir = () => new Promise((coz) => {
  const r = request({ port: nokta, path: "/odunc", agent: false }, (y) => {
    y.resume();
    y.on("end", () => { if (y.statusCode === 200) yanitlanan += 1; else reddedilen += 1; coz(); });
    y.on("aborted", () => { dusen += 1; coz(); });
  });
  r.on("error", () => { dusen += 1; coz(); });
  r.end();
});

if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/yuk.mjs <nokta> <kip> <adet> <aralik> <etiket>");
else {
  if (kip === "esz") await Promise.all(Array.from({ length: adet }, bir));
  else {
    const acik = [];
    for (let i = 0; i < adet; i += 1) { acik.push(bir()); await bekle(aralik); }
    await Promise.all(acik);
  }
  console.log(`${etiket} yuk: ${adet} istek, ${yanitlanan} yanit, ${reddedilen} reddedilen, ${dusen} dusen`);
}

Çöken Süreç ve Başlamayan Süreç

İlk düzenek aynı çökmeyi üç politikayla karşılıyor. İşçi her dört yanıttan sonra çıkıyor, yük 24 isteği 100 milisaniye arayla gönderiyor. Ardından aynı üç politika hiç başlamayan bir işçiyle koşuyor: süreç dinlemeye geçmeden çıkıyor.

# olc1.sh — ayni cokme, uc yeniden baslatma politikasi. Isci her 4 yanittan sonra cikiyor.
kos() {                                   # kos <politika> <isci-mod> <adet> <etiket>
  node topoloji/yonetici.mjs 8941 "politika=$1,sure=0" "$2" "$4" & M=$!
  sleep 0.5
  node topoloji/yuk.mjs 8941 aralikli "$3" 100 "$4"
  kill -TERM $M; wait $M; sleep 0.2
}

kos aninda        "cok=4,is=40" 24 "cokme / aninda        "
kos geri-cekilmeli "cok=4,is=40" 24 "cokme / geri cekilmeli"
kos sinirli:2     "cok=4,is=40" 24 "cokme / sinirli (2)   "
kos aninda        "baslarken-cok=1" 8 "baslamiyor / aninda   "
kos geri-cekilmeli "baslarken-cok=1" 8 "baslamiyor / geri cek."
kos sinirli:2     "baslarken-cok=1" 8 "baslamiyor / sinirli 2"
cokme / aninda         yuk: 24 istek, 20 yanit, 0 reddedilen, 4 dusen
cokme / aninda         yonetici: 5 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 8 MB, 0 kayit atildi
cokme / geri cekilmeli yuk: 24 istek, 14 yanit, 0 reddedilen, 10 dusen
cokme / geri cekilmeli yonetici: 3 yeniden baslatma, 700 ms gecikme, 0 oldurme, tepe 8 MB, 0 kayit atildi
cokme / sinirli (2)    yuk: 24 istek, 12 yanit, 0 reddedilen, 12 dusen
cokme / sinirli (2)    yonetici: 2 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 8 MB, 0 kayit atildi, vazgecti
baslamiyor / aninda    yuk: 8 istek, 0 yanit, 0 reddedilen, 8 dusen
baslamiyor / aninda    yonetici: 43 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 0 MB, 0 kayit atildi
baslamiyor / geri cek. yuk: 8 istek, 0 yanit, 0 reddedilen, 8 dusen
baslamiyor / geri cek. yonetici: 4 yeniden baslatma, 1500 ms gecikme, 0 oldurme, tepe 0 MB, 0 kayit atildi
baslamiyor / sinirli 2 yuk: 8 istek, 0 yanit, 0 reddedilen, 8 dusen
baslamiyor / sinirli 2 yonetici: 2 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 0 MB, 0 kayit atildi, vazgecti

İlk üç ikili aynı çökmeyi karşılıyor ve sıralama beklenenin tersine dönüyor. Anında politikası 24 isteğin 20’sini yanıtladı, dördü düştü (bu koşumda). Geri çekilmeli politika 14 yanıt verdi, on istek düştü — merdiven 100, 200 ve 400 milisaniyeyle toplam 700 milisaniyelik bir bekleme ekledi ve o bekleme boyunca gelen her istek düştü. Sınırlı politika iki denemeden sonra vazgeçti ve kalan on iki isteğin hepsi düştü; bu son sayı politikanın tanımından çıkar, koşumdan değil.

Yanıtlanan ve düşen sayılar sabit aralıklı bir yükten okunur ve makinenin o andaki yüküne bağlıdır; koşumdan bağımsız olan, politikaların eklediği bekleme ile deneme sayılarıdır.

Çökme geçici olduğunda sıralama nettir: geri çekilme, bir sonraki denemenin başarılı olacağı durumda saf kayıptır. Merdivenin varlık nedeni son üç satırda görünüyor.

İşçi hiç başlamadığında üç politika da aynı sonucu veriyor — sekiz isteğin sekizi düşüyor. Fark istek tarafında değil, makinenin harcadığı işte: anında politikası aynı 0,8 saniyelik pencerede onlarca süreç başlattı (bu koşumda 43), geri çekilmeli politika dört, sınırlı politika iki. Bu üç sayının ikisi koşumdan bağımsızdır: geri çekilme merdiveni 100, 200, 400 ve 800 ile o pencereye dört deneme sığdırır, sınırlı politika tanımı gereği ikide durur. Anında politikasının sayısı ise pencere uzunluğu ile süreç başlatma maliyetinin oranıdır ve üst sınırı yoktur: pencere büyüdükçe büyür, makine hızlandıkça büyür.

Bir ayrım daha var ve en sessiz olanı odur. Sınırlı politika vazgeçti diye yazıyor; öteki ikisi sonsuza kadar denemeye devam eder. Ayakta olmayan bir sistem, hiç durmadan başlatılmaya çalışıldığı sürece “başlatılıyor” görünür.

Sınırı Aşmak: Öldürmek ve Yavaşlatmak

İkinci düzenek bellek sınırını iki yerde uyguluyor. Sert sınırda yönetici bildirimi okur ve eşiği geçen süreci öldürür. Yumuşak sınırda işçi kendi eşiğini görür, en eski kayıtların yarısını atar ve ayakta kalır. Yirmi dört istek birden gönderiliyor, yani sınır aşıldığında hepsi uçuşta. Üçüncü düzenek durdurma işaretini uçuşta altı istek varken gönderiyor.

# olc2.sh — bellek siniri iki bicimde, ardindan durdurma isaretinde ucustaki istekler.
bellek() {                                # bellek <yonetici-ayar> <isci-mod> <etiket>
  node topoloji/yonetici.mjs 8942 "$1" "$2" "$3" & M=$!
  sleep 0.5
  node topoloji/yuk.mjs 8942 esz 24 0 "$3"
  kill -TERM $M; wait $M; sleep 0.2
}
bellek "politika=aninda,bellek=sert:40,sure=0" "is=200,basamak=100"                "sert sinir 40 MB "
bellek "politika=aninda,bellek=yok,sure=0"     "is=200,basamak=100,bellek=yumusak" "yumusak sinir 40 MB"

kapanis() {                               # kapanis <yonetici-ayar> <isci-mod> <etiket>
  node topoloji/yonetici.mjs 8943 "$1" "$2" "$3" & M=$!
  sleep 0.5
  node topoloji/yuk.mjs 8943 esz 6 0 "$3"
  wait $M; sleep 0.2
}
kapanis "politika=aninda,sure=700,bekleme=500" "is=300,kapanis=zarif" "zarif, bekleme 500 ms"
kapanis "politika=aninda,sure=700,bekleme=50"  "is=300,kapanis=zarif" "zarif, bekleme  50 ms"
kapanis "politika=aninda,sure=700,bekleme=500" "is=300,kapanis=ani"   "ani kapanma          "
sert sinir 40 MB  yuk: 24 istek, 1 yanit, 0 reddedilen, 23 dusen
sert sinir 40 MB  yonetici: 1 yeniden baslatma, 0 ms gecikme, 1 oldurme, tepe 48 MB, 0 kayit atildi
yumusak sinir 40 MB yuk: 24 istek, 24 yanit, 0 reddedilen, 0 dusen
yumusak sinir 40 MB yonetici: 0 yeniden baslatma, 0 ms gecikme, 0 oldurme, tepe 28 MB, 10 kayit atildi
zarif, bekleme 500 ms yuk: 6 istek, 6 yanit, 0 reddedilen, 0 dusen
zarif, bekleme  50 ms yuk: 6 istek, 0 yanit, 0 reddedilen, 6 dusen
ani kapanma           yuk: 6 istek, 0 yanit, 0 reddedilen, 6 dusen

Sert sınır 48 MB’de aşılıyor ve süreç öldürülüyor: yirmi dört isteğin biri yanıtlanıyor, yirmi üçü düşüyor, bir yeniden başlatma yapılıyor. Yumuşak sınırda aynı yirmi dört isteğin yirmi dördü de yanıtlanıyor, hiç düşen yok, hiç yeniden başlatma yok; bedel on kaydın atılması ve tepenin 48 yerine 28 MB’de kalmasıdır.

İki satır aynı eşiği aynı yükte karşılıyor ve hangi işin kaybolduğu değişiyor. Öldürmede kaybolan şey uçuştaki isteklerdir: yirmi üç ödünç işlemi yarıda kesilir ve istemci yanıt almaz. Kayıt atmada kaybolan şey bellekteki geçmiştir: süreç yaşar, istek yanıtlanır, atılan kaydın gerektiğinde yeniden okunması gerekir. Birincisi görünür bir arıza üretir, ikincisi bir yavaşlama üretir.

Yirmi üç sayısı sınırın aşıldığı anda uçuşta olan istek sayısıdır: öldürmenin faturası o anda taşınan işle ölçülür.

Yumuşak sınırın sessiz tarafı da buradadır: hiçbir sayaç kırmızıya dönmez. Yirmi dört istek yanıtlandı, düşen yok. Atılan kayıt sayısını raporlamayan bir düzenekte bu satır kusursuz görünür ve bellek eşiğinin sürekli aşıldığı bilgisi hiçbir yere yazılmaz.

Bekleme Süresi

Son üç satır durdurma işaretini uçuşta altı istek varken gönderiyor. İstek başına iş 300 milisaniye.

Zarif kapanmada işçi yeni istek almayı bırakır ve bekleyenleri boşaltır; bekleme 500 milisaniye olduğunda altı isteğin altısı boşaltıldı. Aynı kod, bekleme 50 milisaniyeye çekildiğinde altı isteğin altısını kaybetti: işçi boşaltmaya başladı, yönetici sonunu beklemedi ve süreci öldürdü. Ani kapanmada işçi hiç boşaltmadı ve sonuç aynı oldu.

Üç satırın ikisi aynı sonucu veriyor, ama nedenleri farklı: birinde boşaltma yok, ötekinde boşaltma var ama tanınan süre yetmiyor. Kod tarafında ikincisi doğru yazılmıştır; ayar tarafında ikisi de aynı kadar kayıptır. Bekleme süresi, uygulamanın en uzun isteğinden büyük olmadıkça zarif kapanma kodu çalışmaz — ve kodun çalışmadığı hiçbir yere yazılmaz.

Ayarın Yeri

Ayar Yeri Kaç yerde Yanlış değerin sessiz sonucu
Yeniden başlatma politikası süreç yöneticisi 1 (her düğüm) geçici çökmede fazladan düşen istek, kalıcı çökmede sonsuz başlatma döngüsü
Deneme sınırı süreç yöneticisi 1 sınır yoksa arıza “başlatılıyor” olarak görünür
Bellek sınırı ve biçimi yönetici (sert) ya da uygulama kodu (yumuşak) 2 öldürmede uçuştaki bütün istekler, kayıt atmada sessiz yavaşlama
Bekleme süresi süreç yöneticisi 1 zarif kapanma kodu yazılıdır ve hiç çalışmaz

Dört satırın üçü tek bir yerde durur ve tek bir sayıdır. Etkileri ancak bir çökme ya da bir yayın sırasında görünür.

Özet

  • Aynı geçici çökmede anında yeniden başlatma 24 isteğin 20’sini yanıtladı, geri çekilmeli politika 14’ünü, iki denemeyle sınırlı politika 12’sini (ilk ikisi bu koşumda); geri çekilmenin eklediği 700 milisaniyelik bekleme doğrudan düşen isteğe dönüştü.
  • Süreç hiç başlamadığında üç politika da sekiz isteğin sekizini kaybetti; fark harcanan işte oldu — anında politikası aynı pencerede onlarca süreç başlattı (bu koşumda 43), geri çekilmeli dört, sınırlı iki, ve yalnız sonuncusu vazgeçtiğini bildirdi.
  • Geri çekilme merdiveninin ve deneme sınırının ürettiği sayılar koşumdan bağımsızdır; anında politikasının başlatma sayısının üst sınırı yoktur.
  • Bellek sınırı sert uygulandığında yirmi dört isteğin yirmi üçü düştü ve bir yeniden başlatma yapıldı; yumuşak uygulandığında hiç istek düşmedi ve bedel on kaydın atılması oldu. Düşen istek sayısı, sınırın aşıldığı anda uçuşta olan iş kadardır.
  • Zarif kapanmada 500 milisaniyelik bekleme uçuştaki altı isteğin altısını boşalttı; aynı kod 50 milisaniyelik beklemeyle altısını da kaybetti ve sonuç ani kapanmayla aynı çıktı.

Sonraki Adım

Bu iki ders tek bir süreçle çalıştı. Vekil tek bir hedefe iletti, yönetici tek bir işçiyi başlattı ve bellek sınırı o tek sürecin sınırıydı. Yükü karşılamanın iki yolu vardır ve ikisi de bu tek sayıya dokunur: sürece daha çok kaynak vermek ya da daha çok süreç açmak. İki yol aynı isteği karşılar, aynı yanıtı döndürür ve arka planda birbirinden çok farklı bir fatura üretir. Sonraki ders aynı yükü tek büyük süreçle ve dört küçük süreçle karşılar; bellek toplamını, ayağa kalkma süresini ve her sürecin kendi bağlantı havuzunu açmasından doğan çarpanı ölçer — bir sürecin havuz ayarı doğruyken dört sürecin aynı ayarı depoya dört katı bağlantı gönderir.

İ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