Ders 13 / 19
Yeniden Deneme ve Geri Çekilme
Yeniden denemenin hangi katmanda durduğunun ölçülmesi: aynı kuralın istemci sarmalayıcısında, ara katmanda ve ağ geçidinde ayrı ayrı kurulması, sağlıklı çağrı yerinin yeniden denenip denenmediği, üst üste binen üç yerleşimin çarpımı ve tekrarlanan yan etkinin teslim cinsinden sayılması.
İçindekiler
Bir önceki ders süre sınırını koydu ve çağıranı serbest bıraktı, ama elinde bir hata kaldı. En yakın karşılık yeniden denemektir: bağımlılık geçici olarak düşmüşse ikinci çağrı başarılı olabilir. Bu kararın da bir yeri vardır ve zaman aşımından daha tehlikelidir — zaman aşımı bağımlılığın yükünü değiştirmiyordu, yeniden deneme onu doğrudan çarpar.
Üstel geri çekilme, sarsıntı, tavan ve deneme bütçesi aritmetiği M14/K06 Uygulama Mimarisi, M16/K05 ve M19/K05 Dayanıklılık ve Güvenilirlik kurslarında kuruldu; kalıbın sistem üzerindeki etkisi de M19/K05’te ölçüldü. Burada tekrarlanmaz. Bu dersin sorusu tektir: yeniden deneme hangi katmanda duruyor ve o katman değiştiğinde bağımlılığa giden çağrı ne oluyor.
Aynı kural üç yere kurulabilir. İstemci sarmalayıcısı tek bir dış çağrıyı yeniden dener. Ara katman isteğin gövdesini bütünüyle yeniden çalıştırır. Ağ geçidi servisin tamamını yeniden çağırır ve servisin kodunda tek satırı yoktur. Üçü de aynı sözü verir; üçü de farklı bir şeyi tekrarlar.
Düzenek
Bildirim bağımlılığı bu dersin ölçüsünü taşır. DY5 — bildirim teslimi gerçekten yapar, kaybolan
şey onaydır: her çağrıda teslim sayacı artar, ama ilk ariza çağrıda 503 döner. Böylece yeniden
denemenin tekrarladığı şey yalnız çağrı değil, yan etkidir. Üyelik sağlıklı bir bağımlılıktır
ve her zaman yanıt verir.
// servisler.mjs — 8202 bildirim (arizali, teslimi sayar), 8203 uyelik (saglikli), // 8201 odunc: iki yeniden deneme yerlesimini de tasir. Sayaclar 8202/sayac ucundadir. import { createServer, request } from "node:http"; const s = { odunc: 0, uyelik: 0, bildirim: 0, teslim: 0, kalan: 0 }; // teslim: bildirimin gercekten gonderdigi createServer((i, y) => { // 8202 bildirim const q = new URL(i.url, "http://x").searchParams; if (i.url === "/sayac") return y.end(JSON.stringify(s)); if (i.url.startsWith("/sifirla")) { for (const k of ["odunc", "uyelik", "bildirim", "teslim"]) s[k] = 0; s.kalan = Number(q.get("ariza")); return y.end("{}"); } s.bildirim += 1; s.teslim += 1; // teslim yapilir; onay ilk `kalan` cagride kaybolur if (s.kalan > 0) { s.kalan -= 1; y.statusCode = 503; } y.end("{}"); }).listen(8202); createServer((i, y) => { s.uyelik += 1; y.end("{}"); }).listen(8203); // saglikli bagimlilik const cagir = (port, yol) => new Promise((coz) => { const r = request({ port, path: yol, agent: false }, (y) => { y.resume(); y.on("end", () => coz(y.statusCode)); }); r.on("error", () => coz(0)); r.end(); }); // >>> yerlesim: istemci sarmalayicisi — yalniz sardigi cagri yerini yeniden dener const istek = async (port, yol, deneme) => { for (let n = 1; ; n += 1) { const k = await cagir(port, yol); if (k < 500 || n >= deneme) return k; } }; // <<< const isle = async (ic) => { // odunc isteginin govdesi: iki dis cagri yeri await istek(8203, "/uye", 1); // saglikli bagimlilik, yeniden denenmez return istek(8202, "/gonder", ic); }; createServer(async (i, y) => { // 8201 odunc const q = new URL(i.url, "http://x").searchParams; s.odunc += 1; // >>> yerlesim: ara katman — istegin govdesini butunuyle yeniden calistirir let k = 0; for (let n = 1; n <= Number(q.get("ara")); n += 1) { k = await isle(Number(q.get("ic"))); if (k < 500) break; } // <<< y.statusCode = k; y.end("{}"); }).listen(8201);
Üçüncü yerleşim ölçüm betiğindedir, çünkü ağ geçidi servisin dışındadır. DY6 — her katmanın
deneme sayısı 3’tür; kıyas ancak sayı sabitken anlamlıdır. >>> yerlesim: işaretleri kod bedelini
saymak içindir, betik onları kaynaktan okur.
// olc.mjs — yeniden denemenin hangi katmanda durdugu, ust uste binen denemenin carpimi, kod bedeli. import { readFileSync } from "node:fs"; import { request } from "node:http"; const cagir = (port, yol) => new Promise((coz) => { const r = request({ port, path: yol, agent: false }, (y) => { y.resume(); y.on("end", () => coz(y.statusCode)); }); r.on("error", () => coz(0)); r.end(); }); const oku = (yol) => new Promise((c) => request({ port: 8202, path: yol }, (r) => { let g = ""; r.on("data", (p) => (g += p)); r.on("end", () => c(g === "{}" ? null : JSON.parse(g))); }).end()); const yaz = (g, a, ...r) => console.log(String(a).padEnd(24) + r.map((x) => String(x).padStart(g)).join("")); // >>> yerlesim: ag gecidi — servisin kodunda tek satir yoktur const gecit = async (dis, ara, ic) => { for (let n = 1; ; n += 1) { const k = await cagir(8201, `/odunc?ara=${ara}&ic=${ic}`); if (k < 500 || n >= dis) return k; } }; // <<< const dene = async (ariza, dis, ara, ic) => { await oku(`/sifirla?ariza=${ariza}`); const k = await gecit(dis, ara, ic); return { k, ...(await oku("/sayac")) }; }; console.log("gecici ariza: bildirim ilk 2 cagride teslim eder ama onay kaybolur; tek kullanici istegi"); yaz(15, "yerlesim (dis/ara/ic)", "sonuc", "odunc cagri", "uyelik cagri", "bildirim cagri", "teslim"); for (const [ad, d, a, i] of [["yok (1/1/1)", 1, 1, 1], ["sarmalayici (1/1/3)", 1, 1, 3], ["ara katman (1/3/1)", 1, 3, 1], ["ag gecidi (3/1/1)", 3, 1, 1]]) { const r = await dene(2, d, a, i); yaz(15, ad, r.k, r.odunc, r.uyelik, r.bildirim, r.teslim); } console.log("\nkalici ariza: ust uste binen yeniden deneme"); yaz(15, "dis / ara / ic", "carpim", "bildirim cagri", "teslim", "uyelik cagri"); for (const [d, a, i] of [[1, 1, 1], [3, 1, 1], [3, 3, 1], [3, 3, 3], [5, 3, 3]]) { const r = await dene(999, d, a, i); yaz(15, `${d} / ${a} / ${i}`, d * a * i, r.bildirim, r.teslim, r.uyelik); } const kod = (dosya) => [...readFileSync(dosya, "utf8").matchAll(/\/\/ >>> yerlesim: ([^\n—]+)—[^\n]*\n([\s\S]*?)\/\/ <<</g)] .map(([, ad, g]) => [ad.trim(), dosya, g.trim().split("\n").length]); const cy = [...readFileSync("servisler.mjs", "utf8").matchAll(/istek\(\d+, "[^"]+", ([^)]+)\)/g)].map((m) => m[1]); console.log("\nkod bedeli ve kapsam"); yaz(15, "yerlesim", "dosya", "satir"); for (const [ad, d, n] of [...kod("servisler.mjs"), ...kod("olc.mjs")]) yaz(15, ad, d, n); console.log(`odunc govdesinde ${cy.length} dis cagri yeri var; sarmalayici ${cy.filter((x) => x !== "1").length} ` + `tanesini yeniden deniyor, ara katman ve ag gecidi ${cy.length} tanesini de.`); console.log("yapilandirma yuzeyi: 3 deneme sayisi, 3 ayri yerde; hicbiri otekinin degerini gormuyor."); process.exit(0);
node servisler.mjs > /dev/null 2>&1 & SP=$! sleep 1; node olc.mjs; kill $SP
gecici ariza: bildirim ilk 2 cagride teslim eder ama onay kaybolur; tek kullanici istegi yerlesim (dis/ara/ic) sonuc odunc cagri uyelik cagri bildirim cagri teslim yok (1/1/1) 503 1 1 1 1 sarmalayici (1/1/3) 200 1 1 3 3 ara katman (1/3/1) 200 1 3 3 3 ag gecidi (3/1/1) 200 3 3 3 3 kalici ariza: ust uste binen yeniden deneme dis / ara / ic carpim bildirim cagri teslim uyelik cagri 1 / 1 / 1 1 1 1 1 3 / 1 / 1 3 3 3 3 3 / 3 / 1 9 9 9 9 3 / 3 / 3 27 27 27 9 5 / 3 / 3 45 45 45 15 kod bedeli ve kapsam yerlesim dosya satir istemci sarmalayicisi servisler.mjs 6 ara katman servisler.mjs 2 ag gecidi olc.mjs 6 odunc govdesinde 2 dis cagri yeri var; sarmalayici 1 tanesini yeniden deniyor, ara katman ve ag gecidi 2 tanesini de. yapilandirma yuzeyi: 3 deneme sayisi, 3 ayri yerde; hicbiri otekinin degerini gormuyor.
Üç Yerleşim Aynı Arızayı Nasıl Kapatıyor
İlk tablo geçici bir arızayı üç yerleşimle karşılıyor ve sonuç sütunu üçünde de aynı: 503 olan istek 200’e dönüyor. Yeniden deneme sözünü tutuyor. Fark öteki sütunlardadır.
Bağımlılığa giden çağrı üçünde de 3. Bu şaşırtıcı değildir; arıza iki çağrı sürüyor, üçüncüde geçiyor. Yeniden denemenin bağımlılık üzerindeki yükü, yerleşimden değil deneme sayısından gelir.
Sağlıklı bağımlılığa giden çağrı ise 1, 3 ve 3. Ayrım buradadır. İstemci sarmalayıcısı yalnız
sardığı çağrı yerini yeniden dener; üyelik çağrısı bir kez yapılır ve arızayla hiç ilgisi olmayan
o bağımlılık hiç zorlanmaz. Ara katman isteğin gövdesini baştan çalıştırır, dolayısıyla arızasız
çağrı yerini de tekrarlar. Ağ geçidi aynısını yapar ve üstüne ödünç servisini de üç kez çağırır —
tabloda odunc cagri sütununda 1, 1, 3 olarak görünür.
Bundan çıkan kural bir seçim ölçütüdür: dıştaki yerleşim ayrım yapamaz. Ağ geçidi hangi çağrının başarısız olduğunu bilmez, isteğin tamamını tekrarlar; ne kadar dışarı çıkılırsa tekrarlanan iş o kadar büyür. İçteki yerleşim ayrım yapar ama yalnız kendisinden geçen çağrı yerini kapsar — bir önceki dersin atlanan çağrı yeri sorunu burada da aynen geçerlidir.
Teslim sütunu üç yerleşimde de 3. Tek bir kullanıcı isteği için bildirim üç kez gönderilmiştir. Yerleşimi değiştirmek bunu düzeltmiyor; bu yan etki yeniden denemenin kendisinden geliyor. Bir çağrı yerinin tekrarlanabilir olup olmadığı yerleşimin değil, çağrı yerinin özelliğidir ve düzeltmesi de oradadır: o çağrı yeri deneme sayısını 1’de tutmalıdır.
Üst Üste Binen Yeniden Deneme
İkinci tablo üç yerleşimi aynı anda açıyor. Kalıcı arızada her katman kendi payını sonuna kadar harcıyor ve bağımlılığa giden çağrı çarpım sütununu birebir izliyor: 1, 3, 9, 27, 45. Tek bir kullanıcı isteği bağımlılıkta 27 çağrıya dönüşüyor ve tek bir bildirim 27 kez teslim ediliyor.
Bu tablonun asıl bilgisi çarpımın kendisi değil, hiçbir katmanın onu görmemesidir. Ağ geçidini yazan üç deneme yazdı, ara katmanı yazan üç deneme yazdı, sarmalayıcıyı yazan üç deneme yazdı; hiçbiri yanlış bir sayı seçmedi. Yapılandırma yüzeyi üç ayrı yerdedir ve hiçbiri ötekinin değerini göremez. Son satır bunun ne kadar hızlı büyüdüğünü söylüyor: dıştaki katman üçten beşe çıktığında toplam 27’den 45’e gidiyor, yani tek bir sayının tek bir yerde değişmesi bağımlılıkta on sekiz çağrı ekliyor.
uyelik cagri sütunu aynı satırlarda 9’dur, 27 değil. Sağlıklı bağımlılık yalnız dış iki katmanın
çarpımı kadar çağrılıyor, çünkü en içteki yerleşim onu tekrarlamıyor. Çarpımın hangi çarpanlarının
hangi çağrı yerine uygulandığı, yerleşimin doğrudan sonucudur.
Kod Bedeli ve Kapsam
Üçüncü tablo yerleşimlerin koddaki büyüklüğünü veriyor: istemci sarmalayıcısı 6 satır, ara katman 2 satır, ağ geçidi 6 satır — ve ağ geçidinin altı satırı servisin kodunda değildir. Bu, dıştaki yerleşimi çekici kılan şeydir: servis hiç değişmeden bütün çağrı yerleri kapsanır, hatta henüz yazılmamış olanlar bile. Bedeli de tam olarak budur; kapsamın seçici olmaması bir eksiklik değil, o yerleşimin tanımıdır.
İçteki yerleşim tersidir: ödünç gövdesindeki iki dış çağrı yerinden birini yeniden deniyor, ötekini bilerek bırakıyor. Seçicilik ancak her çağrı yerine tek tek karar verilerek elde edilir ve bu karar bir kez verilmez — yeni bir çağrı yeri eklendiğinde yeniden verilir.
Sınanabilirlik
Yerleşim, davranışın testte nereye bağlanabileceğini de belirler.
// test/deneme.test.mjs — yerlesimin sinanabilirligi: iddia nereye baglaniyor. import { test } from "node:test"; import assert from "node:assert/strict"; import { request } from "node:http"; const cagir = (port, yol) => new Promise((coz) => { const r = request({ port, path: yol, agent: false }, (y) => { y.resume(); y.on("end", () => coz(y.statusCode)); }); r.on("error", () => coz(0)); r.end(); }); const oku = (yol) => new Promise((c) => request({ port: 8202, path: yol }, (r) => { let g = ""; r.on("data", (p) => (g += p)); r.on("end", () => c(g === "{}" ? null : JSON.parse(g))); }).end()); const kos = async (ariza, dis, ara, ic) => { await oku(`/sifirla?ariza=${ariza}`); for (let n = 1; n <= dis; n += 1) if (await cagir(8201, `/odunc?ara=${ara}&ic=${ic}`) < 500) break; return oku("/sayac"); }; test("sarmalayici yerlesimi: iddia tek cagri yerine baglanir", async () => { assert.equal((await kos(2, 1, 1, 3)).bildirim, 3); assert.equal((await kos(2, 1, 1, 3)).uyelik, 1); // saglikli cagri yeri yeniden denenmez }); test("ag gecidi yerlesimi: ayni iddia ic ayarla degisir", async () => { assert.equal((await kos(999, 3, 1, 1)).bildirim, 3); assert.equal((await kos(999, 3, 1, 3)).bildirim, 9); // ag gecidi ayni, sonuc uc kati }); test("yeniden deneme teslimi cogaltir", async () => { const r = await kos(2, 1, 1, 3); assert.equal(r.teslim, 3); // tek kullanici istegi, uc teslim });
node servisler.mjs > /dev/null 2>&1 & SP=$! sleep 1 node --test --test-force-exit --test-reporter=tap test/deneme.test.mjs | grep -E "^(ok|not ok|# (tests|pass|fail) )" kill $SP
ok 1 - sarmalayici yerlesimi: iddia tek cagri yerine baglanir ok 2 - ag gecidi yerlesimi: ayni iddia ic ayarla degisir ok 3 - yeniden deneme teslimi cogaltir # tests 3 # pass 3 # fail 0
İlk test sarmalayıcı yerleşiminin sınanabilirliğini gösteriyor: iddia tek bir çağrı yerine bağlanır, sayı yereldir ve başka hiçbir ayar onu değiştiremez. İkinci test ağ geçidi yerleşiminin sorununu testin kendisiyle kanıtlıyor: ağ geçidinin ayarı iki koşumda da 3’tür, ama beklenen çağrı sayısı 3’ten 9’a çıkar, çünkü içeride başka bir sayı değişmiştir. Dıştaki yerleşimin davranışı hakkında yazılan bir iddia, kendisinin görmediği bir ayara bağımlıdır ve o ayar değiştiğinde test kırılır. Üçüncü test yan etkiyi doğrudan sabitliyor: bir kullanıcı isteği, üç teslim.
Özet
- Yeniden denemenin aritmetiği ve sistem üzerindeki etkisi M14/K06, M16/K05 ve M19/K05 Dayanıklılık ve Güvenilirlik kurslarında ölçüldü; burada ölçülen hangi katmanda durduğudur.
- Geçici arızada üç yerleşim de isteği kurtarır ve bağımlılığa giden çağrı üçünde de 3’tür; fark sağlıklı bağımlılığa giden çağrıdadır: sarmalayıcıda 1, ara katmanda 3, ağ geçidinde 3. Ağ geçidi ayrıca ödünç servisini 1 yerine 3 kez çağırır.
- Dıştaki yerleşim ayrım yapamaz: hangi çağrının başarısız olduğunu bilmediği için isteğin tamamını tekrarlar. İçteki yerleşim ayrım yapar ama yalnız kendisinden geçen çağrı yerini kapsar.
- Üç yerleşim aynı anda açıldığında bağımlılığa giden çağrı çarpımı birebir izler: 1, 3, 9, 27, 45. Hiçbir katman ötekinin sayısını görmez; dıştaki sayının üçten beşe çıkması bağımlılıkta on sekiz çağrı ekler. Sağlıklı bağımlılık aynı satırlarda 9 çağrı alır, 27 değil.
- Teslim sayacı üç yerleşimde de 3’tür: tekrarlanan yan etki yerleşimin değil, çağrı yerinin sorunudur ve çözümü o çağrı yerinin deneme sayısını 1’de tutmaktır.
- Kod bedeli 6 / 2 / 6 satırdır ve ağ geçidinin altı satırı servisin kodunda değildir. Testte sarmalayıcı yerleşiminin iddiası yereldir; ağ geçidi yerleşiminin iddiası görmediği bir ayara bağlıdır ve o ayar değişince kırılır.
Sonraki Adım
Yeniden deneme geçici arızayı kapatıyor ama kalıcı arızada durumu ağırlaştırıyor: 27 çağrının 27’si de başarısız oldu, hiçbiri bir şey kurtarmadı ve bağımlılık zaten düşmüşken üzerine yirmi yedi kat yük bindi. Eksik olan şey bir sayı değil, bir karardır — bağımlılığın düştüğünü anlayıp çağrıyı hiç yapmamak. Sonraki ders devre kesiciyi koda yerleştirir ve şunu ölçer: kesicinin durumu bağımlılık başına mı yoksa çağrı yeri başına mı tutuluyor, aynı arızada iki yerleşim ne zaman açılıyor, yapılandırma yüzeyi kaç parametre ve yanlış eşik hangi davranışı üretiyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.