Ders 12 / 18
Servis Ağı
Ağ ilgilerinin uygulama kodundan çıkarılması: yeniden deneme, zaman aşımı, güvenli taşıma ve ölçüt toplamanın yan arabaya taşınmasıyla silinen satırın sayılması, geriye kalan sorumluluğun adlandırılması ve ayrı süreçte koşmanın ödettiği durak, süreç ile alan bilgisi kaybının ölçülmesi.
İçindekiler
Önceki dersin bıraktığı yerde üç toplama katmanı vardı ve üçünün de dış çağrısı çıplaktı: ne zaman aşımı, ne yeniden deneme, ne sayaç. Gerçek bir sistemde bu üç işin her servis kodunda tekrar yazılması gerekir. Kütüphane ödünç sisteminde ödünç servisi ücret servisine gidiyor; gidiş bir ağ üzerinden, taşıma güvenli, karşı taraf zaman zaman geçici hata döndürüyor.
Bu dersin sorusu şu: yeniden deneme, zaman aşımı, güvenli taşıma ve ölçüt toplama uygulama kodundan çıkarılıp ana süreçle birlikte koşan ayrı bir sürece verilirse, uygulama kodundan kaç satır silinir, geriye ne kalır ve bunun bedeli nedir. Yan araba ile elçinin tanımı Trafik Katmanı kursunda kuruldu ve orada dağıtım birimi düzeyinde ölçüldü; burada ölçü kod tarafıdır. Her servisin yanında böyle bir süreç koştuğunda ve hepsinin politikası tek bir yerden verildiğinde ortaya çıkan yerleşime servis ağı (service mesh), politikanın verildiği yere denetim düzlemi (control plane) denir.
Varsayımlar: SS13 ücret servisi güvenli taşıma ile konuşur ve sertifika ölçüm sırasında üretilir. SS14 ücret servisi ilk iki okuma isteğini geçici hatayla reddeder; ödeme ucu kaydı işler ama ilk yanıtı kaybolmuş sayılır. SS15 ölçüm süre ölçmez; satır, süreç, durak ve tahsilat sayar.
Ücret Servisi
Ücret servisinin iş ucu güvenli taşıma üzerinde. Yanına düz bir yönetim ucu konuldu; o uç ölçüm düzeneğidir, sayaçları okur ve sıfırlar.
// mesh/ucret.mjs — ucret servisi: is ucu 8751 guvenli tasima, yonetim ucu 8757 duz import { createServer as tls } from "node:https"; import { createServer } from "node:http"; import { readFileSync } from "node:fs"; const sayac = { okuma: 0, odeme: 0, tahsil: 0 }; const sifirla = () => Object.keys(sayac).forEach((k) => (sayac[k] = 0)); tls({ key: readFileSync("mesh/anahtar.pem"), cert: readFileSync("mesh/sertifika.pem") }, (q, y) => { if (q.url === "/ceza/ode") { sayac.odeme += 1; sayac.tahsil += 1; // kayit islenir; ilk yanit yolda kaybolmus sayilir const kod = sayac.odeme === 1 ? 503 : 200; y.writeHead(kod, { "Content-Type": "application/json" }); return y.end(JSON.stringify({ tahsil: sayac.tahsil, tutar: 12.5 })); } sayac.okuma += 1; const kod = sayac.okuma <= 2 ? 503 : 200; // ilk iki okuma istegi gecici olarak reddedilir y.writeHead(kod, { "Content-Type": "application/json" }); y.end(JSON.stringify(kod === 200 ? { borc: 12.5 } : { hata: "gecici" })); }).listen(8751, "127.0.0.1", () => console.log("ucret 127.0.0.1:8751")); createServer((q, y) => { if (q.url === "/sifirla") sifirla(); y.writeHead(200, { "Content-Type": "application/json" }); y.end(JSON.stringify(sayac)); }).listen(8757, "127.0.0.1");
Ağ İlgileri Uygulama Kodunda
İlk gerçekleştirimde ödünç servisi her şeyi kendisi yapıyor. Ağ ilgisi taşıyan satırlar işaretli; ölçüm bu işareti sayacak.
// mesh/odunc-kalin.mjs — butun ag ilgileri uygulama kodunda, 8752 import { createServer } from "node:http"; import { request } from "node:https"; // [tls] import { readFileSync } from "node:fs"; // [tls] const CA = readFileSync("mesh/sertifika.pem"); // [tls] const ZAMAN_ASIMI = 400; // [zaman asimi] const DENEME = 3; // [yeniden deneme] const olcut = {}; // [olcut] const cagir = (yol, yontem) => new Promise((coz, red) => { // [tls] const q = request({ host: "127.0.0.1", port: 8751, path: yol, // [tls] method: yontem, ca: CA }, (y) => { // [tls] let g = ""; y.on("data", (p) => (g += p)); y.on("end", () => coz({ kod: y.statusCode, govde: g })); }); q.setTimeout(ZAMAN_ASIMI, () => q.destroy(new Error("zaman asimi"))); // [zaman asimi] q.on("error", red); // [zaman asimi] q.end(); }); const guvenli = (yontem) => yontem === "GET"; // yalniz etkisiz cagri yinelenir [yeniden deneme] const dene = async (yol, yontem, ad) => { const o = (olcut[ad] ??= { istek: 0, deneme: 0, hata: 0 }); // [olcut] o.istek += 1; // [olcut] let son; for (let i = 0; i < (guvenli(yontem) ? DENEME : 1); i += 1) { // [yeniden deneme] o.deneme += 1; // [olcut] son = await cagir(yol, yontem); if (son.kod < 500) break; // [yeniden deneme] } if (son.kod >= 500) o.hata += 1; // [olcut] return son; }; const json = (y, kod, g) => { y.writeHead(kod, { "Content-Type": "application/json" }); y.end(JSON.stringify(g)); }; createServer(async (q, y) => { const [kok, uye] = q.url.split("/").filter(Boolean); if (kok === "olcut") return json(y, 200, olcut); // [olcut] const is = kok === "ode" ? { yol: "/ceza/ode", yontem: "POST" } : { yol: `/ceza/${uye}`, yontem: "GET" }; const s = await dene(is.yol, is.yontem, kok); json(y, s.kod, { islem: kok, kod: s.kod, govde: s.govde }); }).listen(8752, "127.0.0.1", () => console.log("odunc-kalin 127.0.0.1:8752"));
Aynı İş, İnce Uygulama ve Yan Araba
İkinci gerçekleştirimde iş mantığı satır satır aynı kalıyor; değişen tek şey dene işlevinin
gövdesi. Uygulama artık yerel bir adrese düz bir çağrı yapıyor.
// mesh/odunc-ince.mjs — ag ilgileri uygulama disinda, 8753: yerel adrese duz cagri import { createServer } from "node:http"; const dene = (yol, yontem) => fetch(`http://127.0.0.1:8754${yol}`, { method: yontem }) .then(async (y) => ({ kod: y.status, govde: await y.text() })); const json = (y, kod, g) => { y.writeHead(kod, { "Content-Type": "application/json" }); y.end(JSON.stringify(g)); }; createServer(async (q, y) => { const [kok, uye] = q.url.split("/").filter(Boolean); const is = kok === "ode" ? { yol: "/ceza/ode", yontem: "POST" } : { yol: `/ceza/${uye}`, yontem: "GET" }; const s = await dene(is.yol, is.yontem, kok); json(y, s.kod, { islem: kok, kod: s.kod, govde: s.govde }); }).listen(8753, "127.0.0.1", () => console.log("odunc-ince 127.0.0.1:8753"));
Silinen satırların hepsi yan arabaya taşınıyor. Yan arabanın gördüğü şey bir yol, bir yöntem ve bir durum kodudur; hangi işin yapıldığını bilmez.
// mesh/yan-araba.mjs — yan araba 8754: zaman asimi, yeniden deneme, guvenli tasima, olcut import { createServer } from "node:http"; import { request } from "node:https"; import { readFileSync } from "node:fs"; const CA = readFileSync("mesh/sertifika.pem"); const ZAMAN_ASIMI = 400; const DENEME = 3; const YINELENEN_KOD = [502, 503, 504]; // politika durum kodudur; isin ne oldugu bilinmez const olcut = {}; const cagir = (yol, yontem) => new Promise((coz, red) => { const q = request({ host: "127.0.0.1", port: 8751, path: yol, method: yontem, ca: CA }, (y) => { let g = ""; y.on("data", (p) => (g += p)); y.on("end", () => coz({ kod: y.statusCode, govde: g })); }); q.setTimeout(ZAMAN_ASIMI, () => q.destroy(new Error("zaman asimi"))); q.on("error", red); q.end(); }); createServer(async (q, y) => { if (q.url === "/olcut" || q.url === "/sifirla") { const g = JSON.stringify(olcut); if (q.url === "/sifirla") Object.keys(olcut).forEach((a) => delete olcut[a]); y.writeHead(200, { "Content-Type": "application/json" }); return y.end(g); } const o = (olcut[q.url] ??= { istek: 0, deneme: 0, hata: 0 }); o.istek += 1; let son; for (let i = 0; i < DENEME; i += 1) { o.deneme += 1; son = await cagir(q.url, q.method); if (!YINELENEN_KOD.includes(son.kod)) break; } if (son.kod >= 500) o.hata += 1; y.writeHead(son.kod, { "Content-Type": "application/json" }); y.end(son.govde); }).listen(8754, "127.0.0.1", () => console.log("yan-araba 127.0.0.1:8754"));
Ölçüm
Ölçüm iki yerleşimde de aynı iki işi koşuyor: bir borç sorgusu ve bir ceza ödemesi. Sonra ücret servisinin sayaçlarını, iki taraftaki ölçütleri ve kod satırlarını okuyor.
// mesh/olc.mjs — iki yerlesim ayni isi kosar: sonuc, deneme, tahsil ve silinen satir import { readFileSync } from "node:fs"; const al = async (u) => JSON.parse(await (await fetch(u)).text()); const kod = async (u) => (await fetch(u)).status; const anlamli = (d) => readFileSync(d, "utf8").split("\n").map((s) => s.trim()) .filter((s) => s && !s.startsWith("//") && /[A-Za-z]/.test(s)); const ilgi = (d) => readFileSync(d, "utf8").split("\n") .filter((s) => /\/\/ \[/.test(s)).length; // ag ilgisi olarak isaretli satir const YERLESIM = [["kalin uygulama", 8752, null], ["ince + yan araba", 8753, 8754]]; const sonuc = []; for (const [ad, uygulama, arac] of YERLESIM) { await al("http://127.0.0.1:8757/sifirla"); // ucret sayaclari if (arac) await al(`http://127.0.0.1:${arac}/sifirla`); const borc = await kod(`http://127.0.0.1:${uygulama}/borc/U-41`); const ode = await kod(`http://127.0.0.1:${uygulama}/ode/U-41`); const sayac = await al("http://127.0.0.1:8757/sayac"); const olcut = await al(`http://127.0.0.1:${arac ?? uygulama}/olcut`); sonuc.push({ ad, borc, ode, sayac, olcut, surec: arac ? 3 : 2, durak: arac ? 2 : 1 }); } const B = ["borc kodu", "odeme kodu", "ucrete ulasan", "tahsil", "surec", "yerel durak"]; console.log(`${"yerlesim".padEnd(18)}${B.map((b) => b.padStart(14)).join("")}`); for (const s of sonuc) { console.log(`${s.ad.padEnd(18)}${[s.borc, s.ode, s.sayac.okuma + s.sayac.odeme, s.sayac.tahsil, s.surec, s.durak].map((v) => String(v).padStart(14)).join("")}`); } console.log(""); for (const s of sonuc) { const d = Object.entries(s.olcut).map(([a, o]) => `${a} ${o.istek}/${o.deneme}/${o.hata}`); console.log(`${s.ad.padEnd(18)} olcut (istek/deneme/hata): ${d.join(" ")}`); } const SERVIS = 5; // katalog, uyelik, odunc, ucret, bildirim const k = anlamli("mesh/odunc-kalin.mjs"), i = anlamli("mesh/odunc-ince.mjs"); const y = anlamli("mesh/yan-araba.mjs"); const silinen = k.filter((s) => !i.includes(s)); console.log(`\nodunc-kalin = ${k.length} satir, odunc-ince = ${i.length} satir, ` + `silinen = ${silinen.length}, eklenen = ${i.filter((s) => !k.includes(s)).length}`); console.log(`ag ilgisi diye isaretli satir: uygulama kodunda ${ilgi("mesh/odunc-kalin.mjs")} -> 0`); console.log(`${SERVIS} servis icin ag ilgisi satiri: uygulama tarafinda ${SERVIS * silinen.length}, ` + `yan araba tarafinda ${y.length} (tek dosya)`);
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -subj "/CN=localhost" \ -addext "subjectAltName=IP:127.0.0.1" -keyout mesh/anahtar.pem -out mesh/sertifika.pem 2>/dev/null node mesh/ucret.mjs > /dev/null & node mesh/odunc-kalin.mjs > /dev/null & node mesh/odunc-ince.mjs > /dev/null & node mesh/yan-araba.mjs > /dev/null & for p in 8752 8753 8754 8757; do curl -s --retry 30 --retry-connrefused --retry-delay 0 -o /dev/null http://127.0.0.1:$p/olcut done node mesh/olc.mjs kill $(jobs -p)
yerlesim borc kodu odeme kodu ucrete ulasan tahsil surec yerel durak kalin uygulama 200 503 4 1 2 1 ince + yan araba 200 200 5 2 3 2 kalin uygulama olcut (istek/deneme/hata): borc 1/3/0 ode 1/1/1 ince + yan araba olcut (istek/deneme/hata): /ceza/U-41 1/3/0 /ceza/ode 1/2/0 odunc-kalin = 38 satir, odunc-ince = 13 satir, silinen = 28, eklenen = 3 ag ilgisi diye isaretli satir: uygulama kodunda 18 -> 0 5 servis icin ag ilgisi satiri: uygulama tarafinda 140, yan araba tarafinda 33 (tek dosya)
Silinen Satır ve Kalan Sorumluluk
Uygulama kodu 38 anlamlı satırdan 13’e iniyor; 28 satır siliniyor, yerine 3 satır geliyor. Silinen satırların 18’i ağ ilgisi diye işaretliydi ve uygulama kodunda bu işaretten hiç kalmadı. Ölçünün asıl anlamı çarpanda: aynı 28 satır beş servisin her birinde yazılacağı için uygulama tarafındaki toplam 140 satırdır; yan araba tarafında ise 33 satırlık tek bir dosya vardır ve politikası dışarıdan verilir.
Geriye kalan sorumluluk üç maddedir ve hiçbiri yan arabaya devredilemez. Birincisi, hangi yola
hangi yöntemle gidileceği: /ceza/ode ucunun POST olduğu bilgisi uygulamadadır. İkincisi,
dönen durum kodunun iş açısından ne demek olduğu — 503 gören uygulama kullanıcıya ne
gösterecektir. Üçüncüsü ve en önemlisi, bir çağrının yinelenmesinin güvenli olup olmadığı.
Kalın gerçekleştirimde bu bilgi guvenli işlevinde tek satırdır; ince gerçekleştirimde o satır
silindi ve yerine hiçbir şey konmadı.
Bedel iki sütunda görünüyor. Süreç sayısı 2’den 3’e, isteğin dokunduğu yerel durak 1’den 2’ye çıkıyor. Ücret servisine ulaşan istek 4’ten 5’e çıkıyor. Bu, Trafik Katmanı kursunun dağıtım birimi başına süreç ölçüsünün kod tarafındaki karşılığıdır: aynı iş, bir durak fazlasıyla.
Ölçüt satırları kaybı gösteriyor. Kalın yerleşimde sayaçlar borc ve ode diye, yani işin
adıyla tutuluyor; ince yerleşimde /ceza/U-41 ve /ceza/ode diye, yani ücret servisinin yol
sözlüğüyle. Yan araba “borç sorgusu” ile “ceza ödemesi” arasındaki farkı yalnız yola bakarak
tahmin edebilir. Üye numarası yola girdiği için her üye ayrı bir ölçüt anahtarı üretir; alan
bilgisi olmadan bu anahtarları toplamak da uygulama dışında bir işe dönüşür.
Yeniden Denemenin Bilmediği Şey
Tablonun en pahalı sütunu tahsil sütunudur. İki yerleşim de aynı iki isteği yaptı; kalın
yerleşimde ücret servisi bir tahsilat işledi, ince yerleşimde iki tahsilat işledi.
Nedeni politikanın konumudur. Ücret servisinin ödeme ucu kaydı işliyor ve ilk yanıtı 503
olarak dönüyor — yanıtın yolda kaybolduğu durumun taklidi. Kalın uygulamada guvenli işlevi
POST çağrısını yinelemeye kapatmıştı: ödeme bir kez denendi, uygulamaya 503 döndü, tahsil 1
oldu. Yan araba ise yalnız durum koduna bakıyor; 503 gördü, aynı isteği yineledi, ikinci
denemede 200 aldı ve uygulamaya 200 döndürdü. Ödünç servisi işlemin başarılı olduğunu sanıyor;
üyenin cezası iki kez tahsil edilmiş durumda ve hiçbir tarafta hata kaydı yok.
Doğan arıza kipi budur: ağ katmanında verilen bir yeniden deneme kararı, iş katmanında yinelemeye kapalı olan bir çağrıyı yineler. Kalın yerleşimde böyle bir arıza kipi yoktu, çünkü karar veren kodun elinde çağrının ne olduğu bilgisi vardı.
Çözüm, sorumluluğu bir ölçüde geri çağırmaktır. Yan arabanın politikası yönteme göre daraltılabilir ya da uygulama isteğe bir tekillik anahtarı koyup ücret servisi yinelenen tahsilatı düşebilir. İkisi de bedava değildir: birincisi ağ politikasının içine alan bilgisi koyar, ikincisi ücret servisine defter tutma işi ekler. Ölçülen sonuç şudur: ağ ilgileri uygulamadan çıkarılabilir, ama hangi çağrının yinelenebilir olduğu bilgisi çıkarılamaz; yalnız yer değiştirir.
Özet
- Aynı iş iki yerleşimde koşturuldu: uygulama kodu 38 satırdan 13 satıra indi, 28 satır silindi ve 3 satır eklendi; ağ ilgisi diye işaretli 18 satırdan uygulamada hiçbiri kalmadı.
- Beş servisli sistemde aynı ilgi uygulama tarafında 140 satır tutar; yan araba tarafında 33 satırlık tek dosya vardır ve politikası dışarıdan verilir.
- Uygulamada kalan sorumluluk: yol ile yöntem seçimi, durum kodunun iş anlamı ve bir çağrının yinelenmesinin güvenli olup olmadığı.
- Ödenen bedel: süreç 2’den 3’e, yerel durak 1’den 2’ye, ücret servisine ulaşan istek 4’ten 5’e; ölçüt anahtarları işin adından yol sözlüğüne düştü.
- Doğan arıza kipi: durum koduna bakan yeniden deneme, yinelemeye kapalı ödeme çağrısını yineledi; tahsilat 1 yerine 2 oldu ve iki tarafta da hata kaydı oluşmadı.
Sonraki Adım
İki gerçekleştirimin de sessiz bir ortak kabulü vardı: ücret servisinin adresi 127.0.0.1:8751
diye kodun içinde yazılıydı. Kalın uygulamada bu adres uygulama dosyasında, ince uygulamada yan
araba dosyasında duruyor; iki durumda da tek bir sabittir. Oysa bir servisin tek kopyası
olmayabilir, kopyalar gelip gidebilir ve giden bir kopyanın adresi bir süre daha geçerliymiş
gibi durabilir. Konunun son dersi adresin kodun dışından çözülmesini ele alır: bir kayıt defteri
süreci kurar, kayıtlara bir kira süresi verir, sağlık bilgisini yönlendirmeye bağlar ve bunu
sabit yapılandırmayla iki sayı üzerinden karşılaştırır — bir kopya eklendiğinde dağıtımda
dokunulan dosya ve bir kopya öldüğünde ölü adrese giden istek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.