Ders 17 / 18
Sunucusuz Mimari
Koşum sorumluluğunun uygulamadan çıkması: işlev dosyalarında kalan koşum satırının sayılması, bileşimin bildirime taşınmasının düzenlenen dosya sayısıyla ölçülmesi, soğuk koşumda modül durumunun ürettiği sonuç sapmasının ve durumu dışarı taşımanın bedelinin ölçülmesi, soğuk başlatmanın kurulum sayısıyla sayılıp süresinin bir modele bırakılması.
İçindekiler
Buraya kadarki dört biçemin ortak bir örtük varsayımı vardı: istek gelmeden önce ayakta olan bir süreç. Tek parça düzende bir süreç, mikroservis düzeninde üç süreç elle başlatıldı ve elle durduruldu; olay güdümlü düzende veriyolunun da bir yerde koşması gerekiyordu. Süreci ayakta tutmak, dinlemek ve isteği doğru birime yönlendirmek uygulamanın taşıdığı koddu.
Sunucusuz mimari (serverless architecture) bu kodu uygulamadan alır. Dağıtım birimi bir süreç değil, bir olay ya da istek geldiğinde çağrılan bir işlevdir; süreci açmak, bekletmek, çoğaltmak ve kapatmak çalıştırıcının işidir. Ad sunucunun yokluğunu anlatmaz — sunucu vardır, ama uygulamanın sorumluluk alanında değildir. Bu ders o kaydırmanın iki yüzünü sayar: uygulamadan çıkan koşum satırını ve karşılığında kısıta dönüşen varsayımları.
Kısıta Dönüşen İlke
Sunucu Tarafı Temelleri kursunun On İki Etmen Uygulama İlkeleri dersinde süreçlerin durumsuz ve paylaşımsız tutulması bir ilke olarak sayılmıştı; aynı kursun Uygulama Çalışma Zamanı dersi ölçekleme biriminin süreç olduğunu ölçtü. Sunucusuz biçemde durumsuz süreç bir tercih olmaktan çıkar: çalıştırıcı işlevin örneğini istediği anda açıp kapatabildiği için iki çağrının aynı bellek alanını göreceği garanti edilemez. İlkenin ihlali burada bir üslup kusuru değil, ölçülebilir bir sonuç sapmasıdır.
İkinci kısıt bileşimle ilgilidir. İşlev kendisini bir yola bağlamaz, komşusunu ithal etmez, çağrı sırasını bilmez. Bileşim işlevlerin dışına, çalıştırıcının okuduğu bir bildirime (deployment manifest) taşınır.
Üç İşlev ve Bir Bildirim
Kurulum işinin kaç kez yapıldığını saymak için işlev modülleri paylaşılan bir ize bir satır bırakır. Bu iz ölçüm içindir; işlevin işiyle ilgisi yoktur.
// sunucusuz/sayac.mjs — kurulum izi: her islev modulunun ilk yuklenisi buraya bir iz birakir export const kurulum = [];
Ücretlendirme bağlamının fiyat işlevi tek bir dışa açılım taşır. Sunucu, dinleme, yol çözümleme ve bağlantı noktası kodu yoktur.
// sunucusuz/ucret.mjs — fiyat islevi: sunucu, dinleme ve yol kodu yok; disa yalniz ele acilir import { kurulum } from "./sayac.mjs"; const TARIFE = { kademe: [[1, 3000], [5, 4800], [20, 9600]], bolge: { "34": 100, "06": 115, "35": 125 }, asgari: 2500 }; kurulum.push("fiyat"); // modul yuklenisinde bir kez calisir: kurulum isi export function ele(olay) { const kademe = TARIFE.kademe.find(([k]) => olay.agirlik <= k) ?? [0, 9600]; const taban = Math.max(TARIFE.asgari, Math.round((kademe[1] * TARIFE.bolge[olay.bolge]) / 100)); return { id: olay.id, net: taban - Math.round(taban * Math.min(olay.oran, 0.4)) }; }
Teslimat operasyonunun plan işlevi aynı biçimdedir; ayrı bir dağıtım birimi olduğu tek kanıtı ayrı bir dosya olmasıdır.
// sunucusuz/operasyon.mjs — plan islevi: ayri dagitim birimi, ayni cagri bicimi import { kurulum } from "./sayac.mjs"; const AGAC = { "34": ["34"], "06": ["34", "06"], "35": ["34", "41", "35"] }; kurulum.push("plan"); export function ele(olay) { const rota = AGAC[olay.bolge] ?? ["34"]; return { rota, gun: rota.length, tasiyici: rota.length > 2 ? "MT" : "AN" }; }
Üçüncü işlev hacim indirimi için sözleşme başına ağırlık toplar. Toplama işi doğası gereği çağrılar arasında bir şey hatırlar; aşağıda aynı işin iki tasarımı yan yana durur. Birinci tasarım toplamı modül düzeyinde tutar, ikincisi çalıştırıcının verdiği depoya yazar.
// sunucusuz/hacim.mjs — hacim indirimi islevi: ayni isin modul durumlu ve depo durumlu tasarimi import { kurulum } from "./sayac.mjs"; const toplam = new Map(); // MODUL DURUMU: modul yeniden yuklendiginde bosalir kurulum.push("hacim"); export function ele(olay) { toplam.set(olay.sozlesmeNo, (toplam.get(olay.sozlesmeNo) ?? 0) + olay.agirlik); return { sozlesme: toplam.size, hacim: [...toplam.values()].reduce((t, a) => t + a, 0) }; } export function eleDepolu(olay, depo) { const anahtar = `hacim:${olay.sozlesmeNo}`; depo.yaz(anahtar, (depo.oku(anahtar) ?? 0) + olay.agirlik); const liste = depo.anahtarlar(); return { sozlesme: liste.length, hacim: liste.reduce((t, k) => t + depo.oku(k), 0) }; }
Bildirim iki eşlemeden oluşur: hangi adın hangi dosyanın hangi dışa açılımına karşılık geldiği ve hangi yolun hangi işlev dizisini çalıştırdığı. Bu dosyada tek bir alan kuralı yoktur; yalnız bileşim vardır.
// sunucusuz/yol.mjs — bildirim: hangi ad hangi dosyanin hangi disa acilimi, hangi yol hangi dizi export const bildirim = { islev: { fiyat: "./ucret.mjs#ele", plan: "./operasyon.mjs#ele", hacim: "./hacim.mjs#ele", "hacim-depolu": "./hacim.mjs#eleDepolu", }, yol: { "/teklif": ["fiyat", "plan"], "/kayit": ["fiyat", "hacim"], "/kayit-depolu": ["fiyat", "hacim-depolu"], }, };
Çalıştırıcının Yerel Durağı
Çalıştırıcı bu derste yerel bir durakla modellenir. Yaptığı iş üç adımdır: bildirimden işlevin
yerini bulmak, modülü yüklemek ve gövdeyi serileştirip çözerek işleve vermek. Kap (container)
burada işlevin içinde koştuğu, çalıştırıcının açıp kapattığı örnektir. soguk seçeneği kap
kararını taklit eder: kapalıyken modül kap ömrü boyunca bir kez kurulur, açıkken her çağrıda
yeniden kurulur. Sorgu eki modülün yeniden kurulmasını sağlar; gerçek bir çalıştırıcıda bunun
karşılığı yeni bir kap açmaktır.
// sunucusuz/calistirici.mjs — islev calistiricisinin yerel duragi: bildirimi okur, islevleri cagirir let sira = 0; // surec boyunca artan yukleme numarasi export function calistirici(bildirim, kayit, { soguk = false } = {}) { const kap = ++sira; // bu ornek bir kap: sicak kosumda modul bir kez kurulur const bellek = new Map(); const ham = new Map(); const depo = { oku(k) { kayit.depo += 1; return ham.get(k); }, yaz(k, v) { kayit.depo += 1; ham.set(k, v); }, anahtarlar() { kayit.depo += 1; return [...ham.keys()]; }, }; return { async cagir(yolAdi, olay) { const sonuc = {}; for (const ad of bildirim.yol[yolAdi]) { const [dosya, disa] = bildirim.islev[ad].split("#"); if (soguk || bellek.has(ad) === false) { bellek.set(ad, await import(`${dosya}?k=${soguk ? ++sira : kap}`)); } const metin = JSON.stringify(olay); kayit.donusum += 2; kayit.bayt += metin.length; kayit.gecis.push(`${yolAdi} -> ${ad}`); sonuc[ad] = bellek.get(ad)[disa](JSON.parse(metin), depo); } return sonuc; }, }; }
Birleştirici bir işlev yoktur. Yanıt nesnesini çalıştırıcı kurar: yolun dizisindeki her adın sonucu kendi adıyla toplanır.
Beş Ölçüm
Sürücü betik beş soruyu yanıtlar: işlev dosyalarında kaç koşum satırı kalmıştır; bir isteğin dokunduğu işlev ve sınırdan geçen bayt kaçtır; aynı üç gönderi sıcak ve soğuk koşumda hangi sonucu verir; durumu depoya taşımanın satır ve erişim bedeli nedir; bileşime bir işlev eklemek kaç işlev dosyası düzenletir.
// sunucusuz/olc.mjs — kosum satiri, dokunulan islev, kurulum sayisi, durum sapmasi, bildirim maliyeti import { readFileSync, writeFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { kurulum } from "./sayac.mjs"; import { calistirici } from "./calistirici.mjs"; import { bildirim } from "./yol.mjs"; const GONDERI = [ { id: "G1", agirlik: 4, bolge: "35", sozlesmeNo: "S7", oran: 0.15 }, { id: "G2", agirlik: 1, bolge: "34", sozlesmeNo: "S7", oran: 0 }, { id: "G3", agirlik: 12, bolge: "06", sozlesmeNo: "S9", oran: 0.25 }, ]; const ISLEV = ["sunucusuz/ucret.mjs", "sunucusuz/operasyon.mjs", "sunucusuz/hacim.mjs"]; const KOSUM = /createServer|\.listen\(|writeHead|yanit\.end|process\.argv/; const CAGIRMA = /await import|bildirim\.|JSON\.(stringify|parse)/; const ozet = (d) => createHash("sha256").update(readFileSync(d)).digest("hex").slice(0, 12); for (const d of [...ISLEV, "sunucusuz/calistirici.mjs"]) { const s = readFileSync(d, "utf8").split("\n").filter((x) => x.trim() !== ""); const say = (r) => s.filter((x) => r.test(x)).length; console.log(`${d.padEnd(26)} satir = ${String(s.length).padStart(2)}, kosum = ${say(KOSUM)}, cagirma = ${say(CAGIRMA)}`); } async function kos(harita, yolAdi, gonderiler, soguk) { const k = { gecis: [], donusum: 0, bayt: 0, depo: 0 }; const oncesi = kurulum.length; const c = calistirici(harita, k, { soguk }); let son; for (const g of gonderiler) son = await c.cagir(yolAdi, g); return { k, kurulum: kurulum.length - oncesi, son }; } const bicim = (ad, r) => `${ad.padEnd(20)} sinir gecisi = ${r.k.gecis.length}, donusum = ${r.k.donusum}, bayt = ${r.k.bayt}, kurulum = ${r.kurulum}, depo erisimi = ${r.k.depo}`; const t = await kos(bildirim, "/teklif", [GONDERI[0]], false); console.log(bicim("/teklif sicak", t)); console.log(` sonuc = ${JSON.stringify(t.son)}`); const sicak = await kos(bildirim, "/kayit", GONDERI, false); const soguk = await kos(bildirim, "/kayit", GONDERI, true); const depolu = await kos(bildirim, "/kayit-depolu", GONDERI, true); for (const [ad, r, anahtar] of [["/kayit sicak", sicak, "hacim"], ["/kayit soguk", soguk, "hacim"], ["/kayit-depolu soguk", depolu, "hacim-depolu"]]) { console.log(`${bicim(ad, r)}\n ${anahtar} = ${JSON.stringify(r.son[anahtar])}`); } console.log(`modul durumunun sapmasi = ${sicak.son.hacim.hacim - soguk.son.hacim.hacim} birim agirlik, ${sicak.son.hacim.sozlesme - soguk.son.hacim.sozlesme} sozlesme`); const kaynak = readFileSync("sunucusuz/hacim.mjs", "utf8"); const satir = (ad) => kaynak.split(`export function ${ad}(`)[1].split("\n}")[0].split("\n").filter((s) => s.trim() !== "").length; console.log(`durumu disariya tasimanin bedeli = ${satir("eleDepolu") - satir("ele")} satir`); const oncekiOzet = ISLEV.map(ozet); const eski = readFileSync("sunucusuz/yol.mjs", "utf8"); writeFileSync("sunucusuz/yol.mjs", eski.replace('"plan"]', '"plan", "hacim"]')); const yeni = readFileSync("sunucusuz/yol.mjs", "utf8").split("\n"); const degisen = eski.split("\n").filter((s, i) => s !== yeni[i]).length; const { bildirim: genis } = await import("./yol.mjs?s=2"); const g = await kos(genis, "/teklif", [GONDERI[0]], false); writeFileSync("sunucusuz/yol.mjs", eski); console.log(`bildirime hacim eklendi: duzenlenen islev dosyasi = ${ISLEV.filter((d, i) => ozet(d) !== oncekiOzet[i]).length}, bildirimde degisen satir = ${degisen}`); console.log(bicim("/teklif yeniden", g)); console.log(` gecis = ${g.k.gecis.join(", ")}`);
node sunucusuz/olc.mjs
sunucusuz/ucret.mjs satir = 9, kosum = 0, cagirma = 0
sunucusuz/operasyon.mjs satir = 8, kosum = 0, cagirma = 0
sunucusuz/hacim.mjs satir = 14, kosum = 0, cagirma = 0
sunucusuz/calistirici.mjs satir = 29, kosum = 0, cagirma = 5
/teklif sicak sinir gecisi = 2, donusum = 4, bayt = 132, kurulum = 2, depo erisimi = 0
sonuc = {"fiyat":{"id":"G1","net":5100},"plan":{"rota":["34","41","35"],"gun":3,"tasiyici":"MT"}}
/kayit sicak sinir gecisi = 6, donusum = 12, bayt = 392, kurulum = 2, depo erisimi = 0
hacim = {"sozlesme":2,"hacim":17}
/kayit soguk sinir gecisi = 6, donusum = 12, bayt = 392, kurulum = 6, depo erisimi = 0
hacim = {"sozlesme":1,"hacim":12}
/kayit-depolu soguk sinir gecisi = 6, donusum = 12, bayt = 392, kurulum = 6, depo erisimi = 13
hacim-depolu = {"sozlesme":2,"hacim":17}
modul durumunun sapmasi = 5 birim agirlik, 1 sozlesme
durumu disariya tasimanin bedeli = 2 satir
bildirime hacim eklendi: duzenlenen islev dosyasi = 0, bildirimde degisen satir = 1
/teklif yeniden sinir gecisi = 3, donusum = 6, bayt = 198, kurulum = 3, depo erisimi = 0
gecis = /teklif -> fiyat, /teklif -> plan, /teklif -> hacim
Sayıların Okunması
İlk dört satır uygulamadan çıkan kodu sayıyor. Üç işlev dosyasının hiçbirinde koşum satırı yok:
createServer, listen, writeHead ya da process.argv geçen satır sayısı 0. Mikroservis
dersinde aynı tarama her dağıtım biriminde 6, üç birimde toplam 18 satır bulmuştu. O satırlar
silinmedi, yer değiştirdi: çalıştırıcının yerel durağı 29 satırdır ve çağırma taraması onda 5 satır
buluyor — hangi işlevin çalışacağına karar veren, gövdeyi serileştiren ve çözen satırlar. Gerçek bir
çalıştırıcıda bu 29 satır uygulamanın deposunda hiç bulunmaz; burada yazılmasının nedeni ölçümün tek
komutla koşabilmesidir.
İkinci kazanç birleştiricinin yokluğudur. Mikroservis düzeninde teklif isteğini karşılayan üçüncü
bir dağıtım birimi vardı: kendi sunucusunu taşıyor, iki servisi adıyla biliyor ve yanıtı
birleştiriyordu. Burada /teklif yolunun karşılığı bildirimdeki tek satırdır ve sonuç nesnesini
çalıştırıcı kurar. Tek istek için sınır geçişi 2, dönüşüm noktası 4, sınırdan geçen 132 bayt —
gönderi başına 66 bayt, iki geçişte iki kez.
Son iki satır bileşimin bildirime taşınmasının bedelini veriyor. Teklif isteğinin hacim indirimini de tetiklemesi istendiğinde bildirimde 1 satır değişti ve işlev dosyalarının özetleri değişmedi: düzenlenen işlev dosyası 0. Olay güdümlü derste aynı ekleme bileşim kökünde 1 satır koda mal olmuştu; burada o satır da koddan çıktı. Karşılığı aynı satırdadır: sınır geçişi 2’den 3’e, dönüşüm 4’ten 6’ya, bayt 132’den 198’e çıktı. Bir isteğin maliyeti artık kodda değil bildirimde okunur, ve bildirim yanlış yazıldığında çözümleyici uyarmaz.
Durumun Gidecek Yeri
Üç /kayit koşumu aynı üç gönderiyi aynı sırayla verdi; üçünde de sınır geçişi 6, dönüşüm 12 ve
bayt 392 çıktı. Çalıştırıcı tarafında hiçbir fark yok. Ayrılan tek şey sonuçtur. Sıcak koşumda hacim
indirimi işlevi {"sozlesme":2,"hacim":17} bildirdi; soğuk koşumda aynı işlev
{"sozlesme":1,"hacim":12} bildirdi. Sapma 5 birim ağırlık ve 1 sözleşmedir. Kaynağı hacim.mjs
dosyasının modül düzeyindeki toplam eşlemesidir: modül her çağrıda yeniden kurulduğu için eşleme
boşaldı ve yalnız son gönderi sayıldı.
Bu, biçemin en sessiz kusurudur. Kod doğru, sonuç yanlış ve hata iletisi yok. Sıcak koşumda aynı kod doğru sonucu verdiği için kusur, çalıştırıcının kap kararına bağlı olarak görünür ya da görünmez — sınama sıcak bir kapta koşuyorsa hiç görünmez.
Düzeltme durumu işlevin dışına almaktır. eleDepolu aynı toplamı çalıştırıcının verdiği depoya
yazar ve soğuk koşumda {"sozlesme":2,"hacim":17} döndürür; sapma 0. Bedeli iki sayıda ölçüldü:
gövde 2 satır uzadı ve üç istek 13 depo erişimi üretti. Modül durumlu tasarımda bu sayı 0’dı. Depo
burada yerel bir eşlemedir; ayrı bir dağıtım birimi olduğunda o 13 erişimin her biri bir sınır
geçişi olur ve mikroservis dersinde sayılan ağ çağrılarına eklenir.
Soğuk Başlatma Bir Model
Kurulum sayısı gerçek bir sayıdır: sıcak koşumda üç istek 2 kurulum yaptı, soğuk koşumda 6. İki işlevin her biri her istekte yeniden kuruldu. Kurulumun süresi ise ölçülmedi. Modülün yüklenme süresi makineye, dosya sistemine ve çalıştırıcının kendi hazırlığına bağlıdır; bu yüzden aşağıdaki anlatım bir modeldir, ölçüm değildir.
Bir çağrının süresi iki parçaya ayrılır: kurulum işi ve asıl iş . Çağrıların oranı yeni bir örnek üzerinde başlıyorsa ortalama süre
olur. Model iki şey söyler. Birincisi, uygulamanın değil trafiğin biçiminin sonucudur: yoğun ve
sürekli trafikte kap yeniden kullanılır, seyrek ve düzensiz trafikte 1’e yaklaşır. Ölçümdeki
soğuk koşum durumunun karşılığıdır. İkincisi, ’yi küçültmenin tek yolu kurulum işini
küçültmektir. ucret.mjs modül düzeyinde yalnız bir tarife nesnesi kurar; aynı yerde büyük bir
tablo ya da bir bağlantı havuzu kurulsaydı büyürdü. Biçemin işlev başına taşınabilecek kurulum
işine koyduğu sınır budur.
Sayılar bir kalite niteliği ödünleşimi betimliyor. Kazanılan nitelik sürdürülebilirliktir: uygulamanın taşıdığı koşum satırı 0, yeni bir bileşim için düzenlenen işlev dosyası 0. Bırakılan nitelikler ikidir. Başarım verimliliğinde öngörülebilirlik gider: aynı istek, çalıştırıcının kap kararına göre 2 ya da 6 kurulum işi yapar ve o kararı uygulama vermez. Güvenilirlikte ise bellekte biriken her değer, ölçülen 5 birimlik sapmanın adayı olur. Bu yüzden biçem çağrı başına bağımsız iş için yerindedir; biriktiren iş ancak durumu dışarı taşıyarak, yani 13 erişimi ödeyerek girer.
Özet
- Sunucusuz mimaride dağıtım birimi bir süreç değil, çağrıldığında koşan bir işlevdir; süreci ayakta tutma ve yönlendirme işi uygulamanın dışına çıkar.
- Üç işlev dosyasında koşum satırı 0 ölçüldü; mikroservis dersinde aynı tarama birim başına 6, toplam 18 satır bulmuştu. O satırların yerel karşılığı 29 satırlık çalıştırıcı durağıdır.
- Birleştirici işlev yoktur:
/teklifyolunun karşılığı bildirimdeki bir satırdır, sonuç nesnesini çalıştırıcı kurar; tek istek 2 sınır geçişi, 4 dönüşüm noktası ve 132 bayt üretti. - Bileşime bir işlev eklemek 0 işlev dosyası düzenletti, bildirimde 1 satır değiştirdi; aynı istek 3 sınır geçişine, 6 dönüşüm noktasına ve 198 bayta çıktı.
- Modül düzeyinde biriken durum soğuk koşumda 5 birim ağırlık ve 1 sözleşme saptı; durumu depoya taşımak sapmayı 0’a indirdi, bedeli 2 satır ve 13 depo erişimi oldu.
- Soğuk başlatmanın sayılan yüzü kurulum sayısıdır: üç istek sıcak koşumda 2, soğuk koşumda 6 kurulum yaptı. Süre bir modelle anlatılır, ölçülmez.
Sonraki Adım
Beş biçem beş ayrı ölçü kümesi bıraktı: birlikte yayınlanan dosya sayısı, sınır geçişi, sınırdan geçen bayt, bir birim durduğunda ayakta kalan birim sayısı, yeni bir yetenek için düzenlenen dosya sayısı. Hiçbir biçem bu ölçülerin hepsinde önde değildi; her kazanç bir başka sayıyı büyüttü. Bu durumda seçim nasıl yapılır ve bir seçimin savunması neye dayanır. Sonraki ders ölçüleri kalite niteliklerine bağlar, aynı kuralları iki sınır yerleşiminde ortak temelde ölçer ve bir biçem kararının hangi eşik altında haklı çıktığını, hangi eşik değiştiğinde döndüğünü gösterir; kursta ölçülen sayılar orada tek bir karar tablosunda toplanır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.