Ders 10 / 18
API Ağ Geçidi
Kenar sorumluluklarının uygulama tarafında toplanmasının ölçülmesi: kimlik ve hız sınırı kodunun kaç serviste kaç satır olarak yinelendiği, sorumluluk ağ geçidine taşındığında değişen dosya, dışarıya açık uç ve istemci çağrısı sayısı, birleştirmenin turlara etkisi ve istek başına eklenen atlama.
İçindekiler
Önceki iki ders sınırın iki yanını çalıştı: sınırın nereden geçeceği, karşıya nasıl çağrı yapılacağı, hangi çağrının hiç yapılmayıp olaya dönüşeceği. Bir taraf hiç ele alınmadı — dışarısı. İstemci servislerin adreslerini tek tek biliyor, her servis kendi kimlik denetimini kendi kodunda taşıyor ve aynı hız sınırı kuralı birden çok yerde yazılı duruyor.
API ağ geçidi, ön yüz için backend ve kapı bekçisi kalıpları Trafik Katmanı kursunda kuruldu ve trafik tarafında ölçüldü; o ölçüm burada tekrarlanmaz. Buradaki soru uygulama tarafındadır: aynı kenar sorumluluğu kaç serviste kaç satır olarak duruyor, tek yere taşındığında kaç dosya ve kaç uç değişiyor, istek başına kaç atlama ekleniyor.
Through-line ödünç sayfasıdır: bir sayfanın çizilmesi için katalog, üyelik ve ödünç servislerinin üçünden de veri gerekir.
Aynı Sorumluluk, İki Yerleşim
Aşağıdaki blok aynı üç servisi iki biçimde yazar. Kenar sorumluluğunu taşıyan satırlar kaynakta işaretlidir; yalın biçim aynı dosyalardan o satırların çıkarılmasıyla üretilir ve işaretli satırlar tek bir ağ geçidi sürecinde bir kez yazılır. İki biçim de gerçekten koşan kaynaktır.
// kur.mjs — ayni uc servisi iki bicimde yazar: kenar sorumlulugu serviste ve gecitte import { mkdirSync, writeFileSync } from "node:fs"; const SERVIS = { katalog: 8961, uyelik: 8962, odunc: 8963 }, GECIT = 8960; const servis = (ad, port) => [ `// servis-${ad}.mjs — odunc sayfasinin bir parcasini ureten servis`, 'import { createServer } from "node:http";', `const PORT = ${port}, IS = 25;`, "let islenen = 0, sifirla = () => {};", "const SAYAC = new Map(), SINIR = 3; sifirla = () => SAYAC.clear(); // kenar: hiz siniri", "createServer(async (istek, yanit) => {", ' if (istek.url === "/sayac") { yanit.end(String(islenen)); return; }', ' if (istek.url === "/sifirla") { islenen = 0; sifirla(); yanit.end("s"); return; }', ' const uye = istek.headers["x-uye"]; // kenar: kimlik', ' if (!uye) { yanit.statusCode = 401; yanit.end("kimlik yok"); return; } // kenar: kimlik', " const n = (SAYAC.get(uye) ?? 0) + 1; SAYAC.set(uye, n); // kenar: hiz siniri", ' if (n > SINIR) { yanit.statusCode = 429; yanit.end("hiz siniri"); return; } // kenar: hiz siniri', " islenen += 1;", " await new Promise((c) => setTimeout(c, IS));", ` yanit.end(JSON.stringify({ ad: "${ad}" }));`, "}).listen(PORT);", ]; const gecit = [ "// gecit.mjs — kenar sorumluluklarini toplayan ve yanitlari birlestiren surec", 'import { createServer } from "node:http";', `const PORT = ${GECIT}, ARDIL = [${Object.values(SERVIS).join(", ")}];`, "let sifirla = () => {};", "const SAYAC = new Map(), SINIR = 3; sifirla = () => SAYAC.clear(); // kenar: hiz siniri", "createServer(async (istek, yanit) => {", ' if (istek.url === "/sifirla") { sifirla(); yanit.end("s"); return; }', ' const uye = istek.headers["x-uye"]; // kenar: kimlik', ' if (!uye) { yanit.statusCode = 401; yanit.end("kimlik yok"); return; } // kenar: kimlik', " const n = (SAYAC.get(uye) ?? 0) + 1; SAYAC.set(uye, n); // kenar: hiz siniri", ' if (n > SINIR) { yanit.statusCode = 429; yanit.end("hiz siniri"); return; } // kenar: hiz siniri', " const parca = await Promise.all(ARDIL.map((p) =>", ' fetch(`http://127.0.0.1:${p}/veri`).then((y) => y.json()))); // kenar: birlestirme', " yanit.end(JSON.stringify({ birlesik: parca }));", "}).listen(PORT);", ]; const kenar = (satirlar) => satirlar.filter((s) => s.includes("// kenar:")).length; const yaz = (dizin, dosya, satirlar) => { mkdirSync(dizin, { recursive: true }); writeFileSync(`${dizin}/${dosya}`, `${satirlar.join("\n")}\n`); return satirlar.length; }; let kenarliSatir = 0, kenarliIsaret = 0, yalinSatir = 0, yalinIsaret = 0; for (const [ad, port] of Object.entries(SERVIS)) { const tam = servis(ad, port), sade = tam.filter((s) => !s.includes("// kenar:")); kenarliSatir += yaz("kenarli", `servis-${ad}.mjs`, tam); kenarliIsaret += kenar(tam); yalinSatir += yaz("yalin", `servis-${ad}.mjs`, sade); yalinIsaret += kenar(sade); } const gecitSatir = yaz("yalin", "gecit.mjs", gecit); const n = Object.keys(SERVIS).length; console.log(`${"bicim".padEnd(22)}${"dosya".padStart(7)}${"satir".padStart(7)}` + `${"kenar satiri".padStart(14)}${"kural degisince dokunulan".padStart(27)}${"disariya acik uc".padStart(18)}`); console.log(`${"kenar servislerde".padEnd(22)}${String(n).padStart(7)}${String(kenarliSatir).padStart(7)}` + `${String(kenarliIsaret).padStart(14)}${String(n).padStart(27)}${String(n).padStart(18)}`); console.log(`${"kenar gecitte".padEnd(22)}${String(n + 1).padStart(7)}` + `${String(yalinSatir + gecitSatir).padStart(7)}${String(yalinIsaret + kenar(gecit)).padStart(14)}` + `${"1".padStart(27)}${"1".padStart(18)}`); console.log(`kenar sorumlulugu ${n} serviste ${kenarliIsaret / n} satir olarak yinelenmisti; ` + `gecitte ${kenar(gecit)} satirda tek kopya`);
bicim dosya satir kenar satiri kural degisince dokunulan disariya acik uc kenar servislerde 3 48 15 3 3 kenar gecitte 4 48 6 1 1 kenar sorumlulugu 3 serviste 5 satir olarak yinelenmisti; gecitte 6 satirda tek kopya
Toplam satır sayısı iki biçimde de aynıdır; taşınan kod yok olmadı, yer değiştirdi. Değişen üç sayı var: yinelenen kenar satırı 15’ten 6’ya indi, kimlik ya da hız sınırı kuralı değiştiğinde dokunulacak dosya sayısı 3’ten 1’e düştü, dışarıya açık uç sayısı 3’ten 1’e indi. Ağ geçidinin altıncı işaretli satırı servislerde hiç bulunmayan bir sorumluluktur: birleştirme.
Koşumdaki Fark
SS4 — hız sınırı üye başına üç istektir. Gerekçe: değer gerçek bir eşik değil, sınırın on iki istekte görünür olmasını sağlayan bir ölçü büyüklüğüdür. Ölçülen nicelik sınırın değeri değil, sınırın kaç yerde uygulandığı ve aynı kuralın dışarıya kaç istek hakkı tanıdığıdır.
// olc.mjs — <olcum: tur|sinir> <kip: dogrudan|gecit>; surecler ayaktayken kosar const IS = 25, S = [8961, 8962, 8963], GECIT = 8960; const BASLIK = { headers: { "x-uye": "7" } }; let cagri = 0, servisCagrisi = 0; const uc = new Set(); const al = async (p, yol) => { cagri += 1; uc.add(p); if (S.includes(p)) servisCagrisi += 1; const y = await fetch(`http://127.0.0.1:${p}${yol}`, BASLIK); await y.text(); return y.status; }; const kimliksiz = async (p, yol) => { // x-uye basligi olmadan istek const y = await fetch(`http://127.0.0.1:${p}${yol}`); await y.text(); return y.status; }; const sifirla = async () => { for (const p of [...S, GECIT]) await fetch(`http://127.0.0.1:${p}/sifirla`).catch(() => {}); }; const ulasan = async () => { let t = 0; for (const p of S) t += Number(await (await fetch(`http://127.0.0.1:${p}/sayac`)).text()); return t; }; const [olcum, kip] = process.argv.slice(2); const YOL = { "dogrudan-sirali": async () => { for (const p of S) await al(p, "/veri"); }, "dogrudan-paralel": () => Promise.all(S.map((p) => al(p, "/veri"))), gecit: () => al(GECIT, "/sayfa"), }; if (olcum === "tur") { console.log(`${"istemci yolu".padEnd(18)}${"istemci cagrisi".padStart(16)}${"ag atlamasi".padStart(13)}` + `${"dokunulan uc".padStart(14)}${"servise ulasan".padStart(16)}${"olculen tur".padStart(13)}`); for (const ad of Object.keys(YOL).filter((a) => a.startsWith(kip))) { const sure = []; for (let i = 0; i < 3; i += 1) { await sifirla(); const t = performance.now(); await YOL[ad](); sure.push(performance.now() - t); } await sifirla(); cagri = 0; servisCagrisi = 0; uc.clear(); await YOL[ad](); const u = await ulasan(); console.log(`${ad.padEnd(18)}${String(cagri).padStart(16)}` + `${String(cagri + u - servisCagrisi).padStart(13)}${String(uc.size).padStart(14)}` + `${String(u).padStart(16)}${String(Math.floor(sure.sort((a, b) => a - b)[1] / IS)).padStart(13)}`); } } else if (olcum === "sinir") { await sifirla(); let kabul = 0, red = 0; for (let i = 0; i < 12; i += 1) { const durum = kip === "gecit" ? await al(GECIT, "/sayfa") : await al(S[i % S.length], "/veri"); if (durum === 200) kabul += 1; else red += 1; } console.log(`hiz siniri (uye basina 3): 12 dis istek -> ${kabul} kabul, ${red} red, ` + `servise ulasan ${await ulasan()}`); await sifirla(); const durum = kip === "gecit" ? [await kimliksiz(GECIT, "/sayfa")] : await Promise.all(S.map((p) => kimliksiz(p, "/veri"))); console.log(`kimliksiz istek: ${durum.length} uc denetledi -> ` + `${durum.filter((d) => d === 401).length} kez 401, servise ulasan ${await ulasan()}`); }
echo "-- kenar sorumlulugu her serviste --" for a in katalog uyelik odunc; do node kenarli/servis-$a.mjs & done sleep 1 node olc.mjs tur dogrudan node olc.mjs sinir dogrudan pkill -f "kenarli/servis" ; sleep 0.3 echo echo "-- kenar sorumlulugu gecitte --" for a in katalog uyelik odunc; do node yalin/servis-$a.mjs & done node yalin/gecit.mjs & sleep 1 node olc.mjs tur gecit node olc.mjs sinir gecit pkill -f "yalin/servis" ; pkill -f "yalin/gecit"
-- kenar sorumlulugu her serviste -- istemci yolu istemci cagrisi ag atlamasi dokunulan uc servise ulasan olculen tur dogrudan-sirali 3 3 3 3 3 dogrudan-paralel 3 3 3 3 1 hiz siniri (uye basina 3): 12 dis istek -> 9 kabul, 3 red, servise ulasan 9 kimliksiz istek: 3 uc denetledi -> 3 kez 401, servise ulasan 0 -- kenar sorumlulugu gecitte -- istemci yolu istemci cagrisi ag atlamasi dokunulan uc servise ulasan olculen tur gecit 1 4 1 3 1 hiz siniri (uye basina 3): 12 dis istek -> 3 kabul, 9 red, servise ulasan 9 kimliksiz istek: 1 uc denetledi -> 1 kez 401, servise ulasan 0
Birleştirme ve Turlar
İlk iki satır istemcinin ödünç sayfasını kendisi topladığı durumdur. İstemci üç çağrı yapıyor, üç ayrı ucu tanıyor ve üç servise ulaşıyor. Çağrıları sırayla yaparsa sayfa üç turda, paralel yaparsa bir turda çiziliyor. Aradaki fark istemcinin elindedir ve sunucu tarafından denetlenemez: aynı sayfanın gecikmesi istemcinin nasıl yazıldığına bağlıdır.
Üçüncü satır aynı sayfayı ağ geçidi üzerinden çiziyor. İstemci bir çağrı yapıyor, tek uç tanıyor, servislere yine üç istek ulaşıyor ve sayfa bir turda dönüyor. Birleştirme sunucu tarafına geçtiğinde en kötü durum en iyi duruma eşitlenir: paralel çağrıyı yapmayı istemcinin bilmesi gerekmez, ağ geçidi zaten yapar. Kazanılan şey ortalama gecikme değil, gecikmenin denetlenebilirliğidir.
Dördüncü kolon bedeli veriyor: istek başına ağ atlaması üçten dörde çıkıyor. Doğrudan yerleşimde istemcinin üç çağrısı doğrudan servislere gidiyordu; geçitli yerleşimde bir dış atlama ve üç iç atlama var. Ağ geçidi kendi işini yapmadığı için bu atlama bir tur eklemedi, ama gecikme bütçesinden pay aldı.
Aynı Kural, İki Farklı Hak
Hız sınırı satırları dersin en keskin sayısını üretiyor. Kural iki yerleşimde birebir aynıdır: üye başına üç istek. On iki istek gönderildiğinde doğrudan yerleşim dokuzunu kabul ediyor, geçitli yerleşim üçünü.
Nedeni sınırın birimidir. Servis başına sayan bir sınır, üç servisli bir sistemde üyeye üç değil dokuz istek hakkı tanır; üye isteklerini servislere dağıttığı sürece hiçbir sınıra çarpmaz. Ağ geçidinde sayan sınır, sayfa isteğine göre sayar. Son kolon iki yerleşimin servis yükünü eşitliyor: her iki durumda da servislere dokuz istek ulaşıyor. Yani aynı yazılı kural, aynı servis yükü altında dışarıya üç kat farklı hak tanıyor. Bir kenar kuralı, uygulandığı yer değişmeden okunamaz.
Kimlik satırı aynı şeyi denetim tarafında gösteriyor. Kimliksiz istek doğrudan yerleşimde üç ayrı uçta üç kez reddediliyor, geçitli yerleşimde bir kez. Üç ret aynı kuralın üç ayrı kopyasıdır ve kopyaların birbirinden ayrılması yalnız zaman meselesidir.
Üçlü
Neyi ucuzlattı. Yinelenen kenar satırı 15’ten 6’ya, kural değiştiğinde dokunulan dosya 3’ten 1’e, dışarıya açık uç 3’ten 1’e, istemci çağrısı 3’ten 1’e indi; sıralı çalışan bir istemcide sayfa 3 turdan 1 tura düştü.
Neyi pahalılaştırdı. İstek başına atlama 3’ten 4’e çıktı, sisteme yeni bir dağıtım birimi ve yeni bir süreç eklendi. Ağ geçidi üç servisin uçlarını kendi kaynağında taşır: bir servisin uç yolu ya da yanıt biçimi değiştiğinde dokunulan dosya sayısı yeniden ikiye çıkar. Kenar sorumluluğunda kazanılan tek dosya, sözleşme tarafında geri verilmiştir.
Hangi yeni arıza kipi doğdu. Dış trafiğin tamamı tek süreçten geçiyor. Önceki dersin ölçüsüyle söylenirse, ağ geçidinin bağımlılık kapanışı bütün giriş uçlarıdır: kapandığında etkilenen istek payı birdir. Doğrudan yerleşimde katalog kapandığında sayfa eksik çiziliyordu; geçitli yerleşimde geçit kapandığında sayfa hiç çizilmiyor. İkinci arıza kipi daha sessizdir: hız sınırı ve kimlik artık servislerde durmadığı için, geçidi atlayan bir çağrı hiçbir denetimden geçmez. Yalın servisler kendilerini savunmaz; savunma tek bir yerin doğru yapılandırılmasına bağlıdır.
Özet
- Kenar sorumluluğu üç serviste 5’er satır olarak yinelenmişti (toplam 15); ağ geçidinde 6 satırda tek kopya oldu ve toplam satır sayısı değişmedi.
- Kimlik ya da hız sınırı kuralı değiştiğinde dokunulan dosya 3’ten 1’e, dışarıya açık uç 3’ten 1’e, istemci çağrısı 3’ten 1’e indi.
- Birleştirme sunucuya geçtiğinde sıralı istemcinin 3 turu 1 tura indi; karşılığında istek başına ağ atlaması 3’ten 4’e çıktı ve yeni bir dağıtım birimi eklendi.
- Aynı “üye başına 3 istek” kuralı, servis başına sayıldığında 12 istekten 9’unu, ağ geçidinde sayıldığında 3’ünü kabul ediyor; servise ulaşan istek her iki durumda da 9.
- Ağ geçidi bütün dış trafiğin bağımlılık kapanışıdır ve yalın servisler kendilerini savunmaz; denetim tek bir yerin doğruluğuna bağlanmıştır.
Sonraki Adım
Ağ geçidi tek bir sayfayı topladı ve o sayfanın tek bir alıcısı olduğu varsayıldı. Kütüphanenin ödünç sayfası ise birden çok yerde çiziliyor: raf başındaki dokunmatik uçbirim yalnız nüsha durumunu ister, elde taşınan bir istemci küçük bir özet ister, personel ekranı üyenin bütün geçmişini ister. Üçü de aynı geçitten geçtiğinde geçidin birleştirme kodu üç ayrı biçimi birden taşımak zorunda kalır ve her istemci değişikliği ortak bir dosyaya dokunur. Sonraki ders bu yükü sayar ve tek bir genel geçit yerine istemci başına ayrı bir toplama katmanı kurmanın kod tarafında neyi ucuzlatıp neyi pahalılaştırdığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.