Ders 09 / 18
Eşzamansız Tümleştirme
Aynı iş akışının eşzamanlı ve olay tabanlı iki gerçekleştirmesinin ölçülmesi: yanıt yolundan düşen iş adımı ve birim sayısı, yayıncının tüketicileri tanımamasıyla doğan sözleşme yükü, olay gövdesindeki her alana bağlı dağıtım birimi sayısı ve yanıtın dönmesiyle etkinin görünmesi arasındaki tutarlılık penceresi.
İçindekiler
Önceki dersin bütün sayıları tek bir varsayımdan doğuyordu: çağıran, ardılın işi bitene kadar bekliyor. Bekleme kaldırıldığında hem tur hem de bağımlılık kapanışı değişir. Bu ders aynı ödünç verme akışını ikinci kez kurar; bu kez bildirim ve ücretlendirme adımları yanıt yolundan çıkarılıp bir olayın tüketicisi hâline getirilir ve iki gerçekleştirim aynı ölçülerle karşılaştırılır.
Kuyruk mekaniği, teslim garantisi, geri basınç ve rekabet eden tüketiciler Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda kuruldu ve ölçüldü; burada tekrarlanmaz. Ölçülen şey taşıyıcının kendisi değil, sınırın iki yanındaki bağın nasıl değiştiğidir.
SS3 — taşıyıcı, bir ekleme kütüğü ve sabit aralıklı yoklama olarak modellenir. Gerekçe: teslim ve sıralama güvenceleri ayrı bir kursta ölçüldüğü için burada taşıyıcının en yalın biçimi yeterlidir; ölçülen üç nicelik (yanıt yolundaki birim, alan başına bağlı birim, tutarlılık penceresi) taşıyıcının türünden bağımsızdır. Kütük gerçek bir dosyadır, yoklama gerçek bir süreçte koşar; model olan yalnız taşıyıcının yalınlığıdır.
İki Gerçekleştirim, Tek Kaynak
Ödünç servisi iki kipte koşar. Eşzamanlı kipte iki ardılını çağırır ve ikisinin de yanıtını bekler. Olay kipinde aynı gövdeyi kütüğe yazar ve döner. Fark tek satırdadır, ama o satır servisin yapılandırmasını da değiştirir: olay kipinde ardıl adresi listesi boştur.
// odunc.mjs — <kip: eszamanli|olay> <port> <bildirim-port> <ucret-port>; uzun omurlu surec import { createServer } from "node:http"; import { appendFileSync } from "node:fs"; const [kip, port, pb, pu] = process.argv.slice(2); if (!port) { console.log("kullanim: node odunc.mjs <kip> <port> <bildirim-port> <ucret-port>"); process.exit(1); } const IS = 25; // odunc kaydinin yazilmasi (ms) const ARDIL = kip === "eszamanli" ? [pb, pu] : []; // olay kipinde yayinci ardil adresi tasimaz let sira = 0; createServer(async (istek, yanit) => { if (istek.url === "/ardil") { yanit.end(JSON.stringify({ ardil: ARDIL.length })); return; } await new Promise((c) => setTimeout(c, IS)); sira += 1; const olay = { tur: "odunc-verildi", sira: sira, uyeId: 7, nushaId: 400 + sira, gecikmeGunu: 0, baslik: "Kitap odunc verildi" }; const govde = JSON.stringify(olay); if (kip === "olay") appendFileSync("olaylar.log", `${govde}\n`); else await Promise.all(ARDIL.map((p) => fetch(`http://127.0.0.1:${p}/is`, { method: "POST", body: govde }))); yanit.end(govde); }).listen(Number(port));
Bildirim ve ücretlendirme birimleri de tek kaynaktan koşar ve iki rolü birden üstlenir: uç olarak çağrıldıklarında gövdeyi istekten alırlar, tüketici olarak koştuklarında aynı gövdeyi kütükten okurlar. İşin kendisi iki durumda da aynı işlevdir; değişen yalnız gövdenin nereden geldiğidir.
// birim.mjs — <ad: bildirim|ucret> <port> <yokla: 0|1>; hem uc olarak hem tuketici olarak kosar import { createServer } from "node:http"; import { appendFileSync, existsSync, readFileSync } from "node:fs"; const [ad, port, yokla] = process.argv.slice(2); if (!port) { console.log("kullanim: node birim.mjs <ad> <port> <yokla>"); process.exit(1); } const IS = 25, ARALIK = 200; // birimin isi ve yoklama araligi (ms) const SOZLESME = { // her birimin olay govdesinden okudugu alanlar bildirim: (o) => `${o.uyeId} nolu uyeye ${o.baslik} bildirimi gitti`, ucret: (o) => `${o.uyeId} nolu uye, nusha ${o.nushaId}, ${o.gecikmeGunu} gun gecikme`, }; const isle = async (o) => { await new Promise((c) => setTimeout(c, IS)); appendFileSync(`etki-${ad}.log`, `${o.sira} ${SOZLESME[ad](o)}\n`); }; createServer(async (istek, yanit) => { let s = ""; for await (const p of istek) s += p; await isle(JSON.parse(s)); yanit.end("tamam"); }).listen(Number(port)); let okunan = 0, mesgul = false; if (yokla === "1") setInterval(async () => { if (mesgul || !existsSync("olaylar.log")) return; mesgul = true; const satir = readFileSync("olaylar.log", "utf8").split("\n").filter(Boolean); while (okunan < satir.length) { await isle(JSON.parse(satir[okunan])); okunan += 1; } mesgul = false; }, ARALIK);
Ölçüm Aracı
Araç iki iş yapar. Koşum kipinde üç istek gönderip turu bulur, sonra etkilerin oturmasını bekler ve bir istek daha göndererek yanıtın döndüğü anda yeni etkinin görünüp görünmediğine bakar. Aynı gözlemi 600 milisaniye sonra yineler. Sözleşme kipinde hiçbir süreç ayakta değildir; yalnız iki kaynak dosya okunur ve olay gövdesindeki her alanın kaç dağıtım birimini bağladığı sayılır.
// olc.mjs — <kip: eszamanli|olay|sozlesme>; ilk iki kip surecler ayaktayken kosar import { existsSync, readFileSync } from "node:fs"; const IS = 25, UC = { odunc: 8951 }, BIRIM = ["bildirim", "ucret"]; const bekle = (ms) => new Promise((c) => setTimeout(c, ms)); const etki = () => BIRIM.reduce((s, a) => s + (existsSync(`etki-${a}.log`) ? readFileSync(`etki-${a}.log`, "utf8").split("\n").filter(Boolean).length : 0), 0); const iste = () => fetch(`http://127.0.0.1:${UC.odunc}/is`).then((y) => y.text()); const [kip] = process.argv.slice(2); if (kip === "eszamanli" || kip === "olay") { const { ardil } = await (await fetch(`http://127.0.0.1:${UC.odunc}/ardil`)).json(); const sure = []; for (let i = 0; i < 3; i += 1) { const t = performance.now(); await iste(); sure.push(performance.now() - t); } const tur = Math.floor(sure.sort((a, b) => a - b)[1] / IS); await bekle(600); const once = etki(); await iste(); const hemen = etki() > once; // yanit dondugu anda yeni etki gorunur mu await bekle(600); const sonra = etki() > once; console.log(`${kip.padEnd(12)}${String(tur).padStart(11)}${String(1 + ardil).padStart(23)}` + `${String(ardil).padStart(27)}${(hemen ? "evet" : "hayir").padStart(19)}` + `${(sonra ? "evet" : "hayir").padStart(19)}${`${etki()}/8`.padStart(13)}`); } else if (kip === "sozlesme") { const olayBlok = readFileSync("odunc.mjs", "utf8").match(/const olay = \{([\s\S]*?)\};/)[1]; const alan = [...olayBlok.matchAll(/(\w+):/g)].map((m) => m[1]); const kaynak = readFileSync("birim.mjs", "utf8"); const tuketici = {}; for (const m of kaynak.matchAll(/^ {2}(\w+): \(o\) => (.*)$/gm)) { tuketici[m[1]] = [...m[2].matchAll(/o\.(\w+)/g)].map((x) => x[1]); } const ortak = [...kaynak.replace(/^ {2}\w+: \(o\) => .*$/gm, "").matchAll(/o\.(\w+)/g)] .map((x) => x[1]); const tk = Object.keys(tuketici); console.log(`${"olay alani".padEnd(14)}${"yayinci".padStart(9)}${"okuyan tuketici".padStart(17)}` + `${"ortak kod".padStart(11)}${"bagli dagitim birimi".padStart(22)}`); let toplam = 0; for (const a of alan) { const okuyan = tk.filter((t) => tuketici[t].includes(a)); const o = ortak.includes(a) ? 1 : 0; const bagli = 1 + (o ? tk.length : okuyan.length); toplam += bagli; console.log(`${a.padEnd(14)}${"1".padStart(9)}${String(okuyan.length).padStart(17)}` + `${String(o).padStart(11)}${String(bagli).padStart(22)}`); } console.log(`${alan.length} alanli olay govdesi, ${tk.length} tuketici birimi; ` + `alan basina ortalama bagli birim ${(toplam / alan.length).toFixed(2)}`); console.log(`hicbir tuketicinin okumadigi alan: ` + `${alan.filter((a) => tk.every((t) => !tuketici[t].includes(a)) && !ortak.includes(a)).join(", ")}`); }
rm -f olaylar.log etki-bildirim.log etki-ucret.log printf '%-12s%11s%23s%27s%19s%19s%13s\n' kip "yanit turu" \ "yanit yolundaki birim" "yayincinin tanidigi ardil" "yanit aninda etki" "600 ms sonra" "etki satiri" node birim.mjs bildirim 8952 0 & node birim.mjs ucret 8953 0 & node odunc.mjs eszamanli 8951 8952 8953 & sleep 1 node olc.mjs eszamanli pkill -f "node birim.mjs" ; pkill -f "node odunc.mjs" ; sleep 0.3 rm -f etki-bildirim.log etki-ucret.log node birim.mjs bildirim 8952 1 & node birim.mjs ucret 8953 1 & node odunc.mjs olay 8951 8952 8953 & sleep 1 node olc.mjs olay pkill -f "node birim.mjs" ; pkill -f "node odunc.mjs" ; sleep 0.3 echo node olc.mjs sozlesme
kip yanit turu yanit yolundaki birim yayincinin tanidigi ardil yanit aninda etki 600 ms sonra etki satiri eszamanli 2 3 2 evet evet 8/8 olay 1 1 0 hayir evet 8/8 olay alani yayinci okuyan tuketici ortak kod bagli dagitim birimi tur 1 0 0 1 sira 1 0 1 3 uyeId 1 2 0 3 nushaId 1 1 0 2 gecikmeGunu 1 1 0 2 baslik 1 1 0 2 6 alanli olay govdesi, 2 tuketici birimi; alan basina ortalama bagli birim 2.17 hicbir tuketicinin okumadigi alan: tur
Yanıt Yolundan Düşen İş
İlk iki kolon kazancı veriyor. Eşzamanlı gerçekleştirmede yanıt iki tur sürüyor: ödünç kaydının yazılması ve ardılların işi. Olay tabanlı gerçekleştirmede aynı istek bir turda dönüyor, çünkü yanıt yolunda yalnız kütüğe bir satır ekleme kalıyor. Son kolon işin kaybolmadığını gösteriyor: her iki gerçekleştirmede de dört istek sekiz etki satırı üretiyor.
Üçüncü kolon kazancın asıl büyüklüğünü veriyor. Yanıt yolundaki dağıtım birimi sayısı üçten bire iniyor. Önceki dersin ölçüsüyle okunduğunda bunun anlamı açıktır: bildirim ve ücretlendirme birimleri ödünç ucunun bağımlılık kapanışından çıkmıştır. Eşzamanlı gerçekleştirmede bu iki birimden birinin kapanması ödünç isteğini düşürürken, olay tabanlı gerçekleştirmede kapanma yanıt yolunda hiç görünmez.
Sözleşmenin Yayılması
Dördüncü kolon bedelin ilk yarısını veriyor. Eşzamanlı gerçekleştirmede yayıncı iki ardıl adresi taşır: kimi çağırdığını bilir, çağrı yerinden okunabilir. Olay kipinde bu sayı sıfırdır. Yayıncı artık tüketicilerini tanımaz; bu tam olarak istenen gevşek bağlanmadır ve tam olarak sözleşme yükünün kaynağıdır.
İkinci tablo bu yükü sayıya çeviriyor. Olay gövdesinde altı alan var ve her alan ortalama 2,17 dağıtım birimini bağlıyor. Üyelik kimliğini iki tüketici birden okuyor: alanın adı ya da anlamı değiştiğinde üç birim birden dokunulmak zorundadır. Sıra numarası hiçbir tüketicinin sözleşme satırında görünmüyor ama ortak işleme kodunda okunuyor, dolayısıyla o da üç birimi bağlıyor.
Son satır eşzamansız tümleştirmenin karakteristik sorunudur: tur alanını hiçbir tüketici
okumuyor. Yayıncı gövdeye koyduğu her alanı sonsuza kadar savunmak zorundadır, çünkü hangi alanın
okunduğunu göremez. Eşzamanlı çağrıda bu bilgi çağrı yerinden okunabilirdi; kütüğün karşı tarafında
okunamaz. Sözleşme evrimi ve tüketici koruması bu kursun son konusunda ele alınacaktır.
Tutarlılık Penceresi
Beşinci ve altıncı kolon yeni arıza kipini gösteriyor. Eşzamanlı gerçekleştirmede yanıt döndüğü an etki hazırdır: çağıran, ücret satırının yazıldığını bilerek döner. Olay tabanlı gerçekleştirmede yanıt döndüğü anda etki yoktur ve 600 milisaniye sonra vardır. Aradaki aralık tutarlılık penceresidir ve bu düzenekte üst sınırı bellidir: yoklama aralığı artı tüketicinin iş süresi, yani 225 milisaniye. Ölçüm bu sınırı iki uçtan doğruluyor — pencerenin başında etki yok, sonunda var.
Pencerenin içinde sistem tutarsızdır ve tutarsızlık gözlenebilir: o aralıkta üyenin hesabını okuyan bir istek ödünç kaydını görür, ücret satırını görmez. Bu, eşzamanlı gerçekleştirmede bulunmayan bir durumdur ve kaldırılamaz; yalnızca daraltılabilir ve arayüzde karşılanabilir. İkinci yeni arıza kipi de aynı yerden çıkar: tüketici kapalıyken olaylar kütükte birikir, yanıt süresi hiç bozulmaz ve arıza yanıt yolunda görünmez. Sorun ancak biriken olaylara bakan biri varsa fark edilir.
Üçlü şöyle toparlanır. Ucuzlayan: yanıt turu 2’den 1’e, yanıt yolundaki birim 3’ten 1’e indi; iki tüketici birimi bağımlılık kapanışından çıktı. Pahalılaşan: sözleşme üç dağıtım birimine yayıldı, alan başına ortalama 2,17 birim bağlandı ve yayıncının tanıdığı ardıl sayısı sıfıra düştüğü için hangi alanın okunduğu koddan görünmez oldu. Doğan arıza kipi: tutarlılık penceresi ve yanıt yolunda görünmeyen tüketici arızası.
Özet
- Aynı iş akışı olay tabanlı kurulduğunda yanıt turu 2’den 1’e, yanıt yolundaki dağıtım birimi 3’ten 1’e iniyor; dört istek her iki gerçekleştirmede de 8 etki satırı üretiyor.
- Yayıncının tanıdığı ardıl sayısı 2’den 0’a düşüyor; gevşek bağlanmanın kaynağı da sözleşme yükünün kaynağı da bu sayıdır.
- Altı alanlı olay gövdesinde alan başına ortalama 2,17 dağıtım birimi bağlı; üyelik kimliği üç
birimi birden bağlıyor ve
turalanını hiçbir tüketici okumuyor. - Tutarlılık penceresi ölçümle iki uçtan doğrulandı: yanıt anında etki yok, 600 ms sonra var; üst sınır yoklama aralığı artı tüketici işidir.
- Eşzamansız tümleştirme tüketici arızasını yanıt yolundan çıkarır; arıza artık yanıt süresinde değil, biriken olaylarda görünür.
Sonraki Adım
İki konu boyunca sınırın iki yanı çalışıldı: sınırın nereden geçeceği, karşıya nasıl çağrı yapılacağı ve bazı çağrıların hiç yapılmayıp olaya dönüştürülmesi. Bütün bu düzenlemelerde bir taraf hiç ele alınmadı — dışarısı. İstemci hâlâ 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. Sonraki ders bu yinelenmeyi sayar: hangi kenar sorumluluğu kaç serviste kaç satır olarak tekrarlanmış, bu sorumluluklar tek bir yere taşındığında kaç dosya ve kaç uç değişiyor ve istek başına kaç atlama ekleniyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.