Ders 19 / 21
Çok Konteynerli Yerel Ortam
Bildirimsel bir bileşim dosyasının üç ölçüsü: başlatma sırasının bağımlılık grafiğinden türetilmesi, hazır olma denetimi olmadan doğan yarış sayısı ile denetimin ayağa kalkmaya eklediği adım, ve elle kuruluma göre ortadan kalkan komut, bayrak ve diziliş kararı.
İçindekiler
Önceki ders tek bir konteynerin içine baktı: on beş girdilik bir ağaç, iki kimlik, bir izin matrisi. Bölgesel ölçüm ağı — sayaç okumalarını toplayan, doğrulayan ve faturaya çeviren kurgu yazılım — tek süreç değildir. Okuma toplayıcının yazdığı kaydı bir doğrulayıcı okur, doğrulanan kayıt faturalamaya gider ve hepsinin altında bir kayıt deposu durur. Bu dördünü yerelde ayağa kaldırmak, dört ayrı başlatma komutu yazmak değildir yalnızca; hangi ağın önce kurulacağına, hangi birimin nereye bağlanacağına ve hangi servisin hangisinden sonra geleceğine karar vermektir. Bu kararlar bir yere yazılmadıkça ortamda kalır: kuran kişinin kabuğunda, geçmiş komutlarında ya da aklında.
Bir bileşim dosyası bu kararları bir tür olarak dosyaya alır. Bu derste böyle bir dosyanın küçük bir şeması tanımlanır ve bir çözümleyici ile ölçülür; şema da çözümleyici de burada yazılmıştır, gerçek bir çalışma zamanı koşturulmaz.
CZ31 — yerel ortamda dört servis vardır: kayıt deposu, okuma toplayıcı, doğrulayıcı, faturalama.
CZ32 — bileşim dosyasının şeması altı alandır: imaj, bağımlı, ağ, birim, kapı, hazır. CZ33 —
bir servisin süreci bir adımda ayağa kalkar, hizmet vermeye başlaması hazır alanı kadar adım daha
sürer. CZ34 — hazır olma denetimi yokken bağımlı servis, bağımlısının süreci başlar başlamaz
bağlanmayı dener. CZ35 — elle kurulumda ağ ve birim ayrı komutlarla oluşturulur, her servis
kendi komutu ve kendi bayraklarıyla başlatılır. CZ36 — bir servisin yeniden başlatılması, ona
geçişli olarak bağımlı bütün servislerin bağlantısını düşürür.
Şema ve Çözümleyici
Şemanın kendisi ölçünün bir parçasıdır: alanlar adlandırıldığı anda, dosyada yazılı olan her şey başlatmadan önce denetlenebilir hâle gelir. Aşağıdaki çözümleyici üç şey yapar — şemaya uymayan alanları ve çözülmeyen gönderileri toplar, bağımlılık grafiğinde döngü arar, sıralamayı dalgalar hâlinde türetir.
// bilesim.mjs — bildirimsel bir bilesim dosyasinin kucuk semasi ve cozumleyicisi (MODEL). // Sema burada tanimlidir: alanlar, tipleri ve gonderme yaptiklari kumeler. export const SEMA = { imaj: 'metin', bagimli: 'liste', ag: 'liste', birim: 'liste', kapi: 'liste', hazir: 'sayi' }; // CZ31: dort servisli kurgu yerel ortam. CZ32: hazir = ayaga kalktiktan sonra kac adimda hizmet verir. export const BILESIM = { aglar: ['olcum-agi'], birimler: ['kayit-verisi'], servisler: { 'kayit-deposu': { imaj: 'kayit:3', bagimli: [], ag: ['olcum-agi'], birim: ['kayit-verisi:/veri'], kapi: [], hazir: 3 }, 'okuma-toplayici': { imaj: 'toplayici:7', bagimli: ['kayit-deposu'], ag: ['olcum-agi'], birim: [], kapi: ['8081'], hazir: 1 }, 'dogrulayici': { imaj: 'dogrulama:4', bagimli: ['kayit-deposu'], ag: ['olcum-agi'], birim: [], kapi: [], hazir: 2 }, 'faturalama': { imaj: 'fatura:2', bagimli: ['okuma-toplayici', 'dogrulayici'], ag: ['olcum-agi'], birim: ['kayit-verisi:/arsiv'], kapi: ['8082'], hazir: 1 }, }, }; export function dogrula(b) { const hata = []; const adlar = Object.keys(b.servisler); for (const [ad, s] of Object.entries(b.servisler)) { for (const alan of Object.keys(SEMA)) if (!(alan in s)) hata.push(`${ad}: eksik alan '${alan}'`); for (const alan of Object.keys(s)) if (!(alan in SEMA)) hata.push(`${ad}: taninmayan alan '${alan}'`); for (const a of s.ag ?? []) if (!b.aglar.includes(a)) hata.push(`${ad}: taninmayan ag '${a}'`); for (const v of s.birim ?? []) { if (!b.birimler.includes(v.split(':')[0])) hata.push(`${ad}: taninmayan birim '${v}'`); } for (const d of s.bagimli ?? []) { if (d === ad) hata.push(`${ad}: kendine bagimli`); else if (!adlar.includes(d)) hata.push(`${ad}: taninmayan bagimlilik '${d}'`); } } return hata; } // Dongu: derinlemesine gezinti; gri bir dugume donus donguyu verir. export function dongu(b) { const renk = {}, yol = []; let bulunan = null; const git = (ad) => { if (renk[ad] === 'gri') { bulunan ??= [...yol.slice(yol.indexOf(ad)), ad]; return; } if (renk[ad] === 'siyah') return; renk[ad] = 'gri'; yol.push(ad); for (const d of b.servisler[ad].bagimli) if (b.servisler[d]) git(d); yol.pop(); renk[ad] = 'siyah'; }; for (const ad of Object.keys(b.servisler)) git(ad); return bulunan; } // Baslatma sirasi grafikten turetilir; ayni dalgadakiler arasinda bagimlilik yoktur. export function sira(b) { const kalan = new Map(Object.entries(b.servisler).map(([a, s]) => [a, new Set(s.bagimli)])); const dalgalar = []; while (kalan.size) { const acik = [...kalan].filter(([, d]) => d.size === 0).map(([a]) => a); if (!acik.length) return null; // dongu var: sira turetilemez dalgalar.push(acik); for (const a of acik) kalan.delete(a); for (const d of kalan.values()) for (const a of acik) d.delete(a); } return dalgalar; } // Elle baslatan kisinin icinden secmek zorunda oldugu dizilis sayisi. export function dizilis(b) { const adlar = Object.keys(b.servisler); const perm = (a) => (a.length <= 1 ? [a] : a.flatMap((x, i) => perm([...a.slice(0, i), ...a.slice(i + 1)]).map((r) => [x, ...r]))); const tum = perm(adlar); const gecerli = tum.filter((p) => p.every((ad, i) => b.servisler[ad].bagimli.every((d) => p.indexOf(d) < i))); return { tum: tum.length, gecerli: gecerli.length }; } if (import.meta.url === `file://${process.argv[1]}`) { console.log(`bilesim dogrulamasi: ${dogrula(BILESIM).length} hata`); const BOZUK = structuredClone(BILESIM); BOZUK.servisler['dogrulayici'].ag.push('rapor-agi'); BOZUK.servisler['faturalama'].bagimli.push('arsivleyici'); BOZUK.servisler['faturalama'].birim.push('gecici-veri:/tmp'); BOZUK.servisler['okuma-toplayici'].yenidenBaslat = 'her-zaman'; delete BOZUK.servisler['kayit-deposu'].hazir; const hatalar = dogrula(BOZUK); console.log(`bozuk bilesim: ${hatalar.length} hata, hepsi baslatmadan once`); for (const h of hatalar) console.log(` ${h}`); const DONGULU = structuredClone(BILESIM); DONGULU.servisler['kayit-deposu'].bagimli = ['faturalama']; console.log(`\ndongu: ${dongu(DONGULU).join(' -> ')}`); console.log(`donguluden sira turetilebilir mi: ${sira(DONGULU) === null ? 'hayir' : 'evet'}`); const d = dizilis(BILESIM); console.log(`\nbaslatma dalgalari: ${sira(BILESIM).map((w) => `[${w.join(' ')}]`).join(' -> ')}`); console.log(`${Object.keys(BILESIM.servisler).length} servisin ${d.tum} dizilisinden ${d.gecerli} tanesi gecerli`); }
bilesim dogrulamasi: 0 hata bozuk bilesim: 5 hata, hepsi baslatmadan once kayit-deposu: eksik alan 'hazir' okuma-toplayici: taninmayan alan 'yenidenBaslat' dogrulayici: taninmayan ag 'rapor-agi' faturalama: taninmayan birim 'gecici-veri:/tmp' faturalama: taninmayan bagimlilik 'arsivleyici' dongu: kayit-deposu -> faturalama -> okuma-toplayici -> kayit-deposu donguluden sira turetilebilir mi: hayir baslatma dalgalari: [kayit-deposu] -> [okuma-toplayici dogrulayici] -> [faturalama] 4 servisin 24 dizilisinden 2 tanesi gecerli
Beş hatanın ortak özelliği, hepsinin başlatmadan önce bulunmasıdır. Elle kurulumda bu beşi bulmanın tek yolu beş ayrı koşumdur: tanınmayan ağ adı ilk servis başlatılırken, çözülmeyen birim adı üçüncü komutta, eksik bağımlılık ise ancak faturalama bağlanmaya çalıştığında görünür. Bildirim biçimi bunu bir okumaya indirir çünkü dosya, kurulumun tamamını aynı anda görür. Kendine bağımlı servis denetimi de aynı yerdedir; bir servisin kendini beklemesi çalışma anında sonsuz bir bekleme, dosyada bir satırdır.
Döngü çıktısı bu iki biçimin en keskin ayrımıdır. kayit-deposu faturalamaya bağımlı yapıldığında
üç kenarlık bir çevrim doğar ve sıralama türetilemez. Elle başlatan kişi bu durumu bir hata olarak
görmez; birinci servisin ayağa kalkmasını bekler, kalkmadığını görür, ikinciyi dener ve iki servis
birbirini beklerken hangisinin önce gelmesi gerektiğine dair bir yanıt bulamaz. Grafik, yanıtın
olmadığını üç adımda söyler.
Son satır kararların sayısını veriyor. Dört servisin yirmi dört dizilişinden yalnız ikisi bütün bağımlılıkları sağlar. Sıra bir tercih değil, grafikten çıkan bir sonuçtur ve dalgalar bunu görünür kılıyor: kayıt deposu yalnız başına, sonra aralarında bağımlılık olmayan iki servis, sonra faturalama.
Yarışlar ve Hazır Olma Denetimi
Sıranın doğru olması yetmez. Bir servisin süreci başlamış olması, hizmet vermeye hazır olduğu anlamına gelmez ve aradaki fark bağımlı servisin bağlanma denemesine düşer. İkinci koşum aynı bileşimi iki kez ayağa kaldırır — hazır olma denetimi olmadan ve onunla.
// kosum.mjs — hazir olma denetiminin bedeli, yeniden baslatmanin yayilimi, komut ve karar sayisi. import { BILESIM, SEMA, sira, dizilis } from './bilesim.mjs'; // CZ33: surec 1 adimda ayaga kalkar, hizmet vermesi 'hazir' adim daha surer. // CZ34: denetim yokken bagimli servis, bagimlisinin sureci basar basmaz baglanmayi dener. function baslat(b, denetim) { const S = b.servisler; const bas = {}, hazirAn = {}; for (const dalga of sira(b)) { for (const ad of dalga) { const d = S[ad].bagimli; bas[ad] = d.length === 0 ? 0 : Math.max(...d.map((x) => (denetim ? hazirAn[x] : bas[x] + 1))); hazirAn[ad] = bas[ad] + S[ad].hazir; } } const yaris = []; for (const [ad, s] of Object.entries(S)) { for (const d of s.bagimli) if (bas[ad] < hazirAn[d]) yaris.push([ad, d, hazirAn[d] - bas[ad]]); } return { bas, yaris, toplam: Math.max(...Object.values(hazirAn)) }; } // Bir servisin yeniden baslatilmasindan gecisli olarak etkilenenler. function etkilenen(b, ad) { const ters = {}; for (const [a, s] of Object.entries(b.servisler)) for (const d of s.bagimli) (ters[d] ??= []).push(a); const kume = new Set(), yigit = [ad]; while (yigit.length) { for (const x of ters[yigit.pop()] ?? []) if (!kume.has(x)) { kume.add(x); yigit.push(x); } } return [...kume]; } const s = (x, n) => String(x).padStart(n); const kenar = Object.values(BILESIM.servisler).reduce((a, x) => a + x.bagimli.length, 0); console.log(`${'denetim'.padEnd(12)}${s('yaris', 7)}${s('kenar', 7)}${s('gizli bekleme', 15)}${s('ayaga kalkma', 14)}`); for (const denetim of [false, true]) { const r = baslat(BILESIM, denetim); const bekleme = r.yaris.reduce((a, [, , g]) => a + g, 0); console.log(`${(denetim ? 'hazir olma' : 'yok').padEnd(12)}${s(r.yaris.length, 7)}${s(kenar, 7)}` + `${s(`${bekleme} adim`, 15)}${s(`${r.toplam} adim`, 14)}`); } for (const [ad, d, g] of baslat(BILESIM, false).yaris) { console.log(` yaris: ${ad.padEnd(17)} -> ${d.padEnd(17)} ${g} adim erken baglanir`); } console.log(`\n${'yeniden baslayan'.padEnd(18)}${s('etkilenen', 10)} ${'kim'}`); for (const ad of Object.keys(BILESIM.servisler)) { const e = etkilenen(BILESIM, ad); console.log(`${ad.padEnd(18)}${s(e.length, 10)} ${e.join(', ') || '-'}`); } // Elle kurulum ile bildirimsel dosyanin yazim bedeli. const servisler = Object.values(BILESIM.servisler); const bayrak = servisler.reduce((a, x) => a + x.ag.length + x.birim.length + x.kapi.length, 0); const komut = BILESIM.aglar.length + BILESIM.birimler.length + servisler.length; const alan = servisler.length * Object.keys(SEMA).length + 2; const d = dizilis(BILESIM); console.log(`\nelle : ${komut} komut, ${bayrak} bayrak, ${d.tum} dizilisten ${d.tum - d.gecerli}'si` + ` en az bir bagimliligi bozar`); console.log(`bildirimsel: 1 komut, ${alan} alanlik dosya, sira turetilir (0 dizilis karari)`); console.log(`yeniden baslatmada elle: en kotu ${Math.max(...Object.keys(BILESIM.servisler) .map((a) => etkilenen(BILESIM, a).length))} servis daha elle baslatilir`);
denetim yaris kenar gizli bekleme ayaga kalkma yok 3 4 5 adim 3 adim hazir olma 0 4 0 adim 6 adim yaris: okuma-toplayici -> kayit-deposu 2 adim erken baglanir yaris: dogrulayici -> kayit-deposu 2 adim erken baglanir yaris: faturalama -> dogrulayici 1 adim erken baglanir yeniden baslayan etkilenen kim kayit-deposu 3 okuma-toplayici, dogrulayici, faturalama okuma-toplayici 1 faturalama dogrulayici 1 faturalama faturalama 0 - elle : 6 komut, 8 bayrak, 24 dizilisten 22'si en az bir bagimliligi bozar bildirimsel: 1 komut, 26 alanlik dosya, sira turetilir (0 dizilis karari) yeniden baslatmada elle: en kotu 3 servis daha elle baslatilir
Denetimsiz düzende sıra doğrudur ve yine de dört bağımlılık kenarının üçü yarış doğurur. Doğru sıra yalnızca sürecin başlamış olmasını güvenceye alır; kayıt deposunun hizmet vermesi üç adım sürer, ona bağlı iki servis birinci adımda bağlanmayı dener ve ikişer adım erken gelir. Faturalama, doğrulayıcıya bir adım erken bağlanır. Toplam beş adımlık bu erkenlik hiçbir yerde bir hata olarak görünmez; yeniden deneme varsa gecikmeye, yoksa başlangıçta düşen bir servise dönüşür.
Denetimin bedeli aynı tablonun son sütununda: ayağa kalkma üç adımdan altı adıma çıkar, iki kat. Bu, bildirimsel biçimin bu derste ödediği en açık bedeldir ve her yerel koşumda ödenir. Karşılığı üç yarışın sıfırlanmasıdır. Sayıların ikisi de aynı dosyadan okunur, bu yüzden takas dosyada görülebilir bir seçim hâline gelir — elle kurulumda ise aynı takas, komutlar arasına serpiştirilen bekleme sürelerinin içinde saklıdır.
Yeniden başlatma tablosu ilk defa tek servisin ötesini ölçüyor. Kayıt deposu yeniden başladığında üç servis etkilenir; okuma toplayıcı ya da doğrulayıcı yeniden başladığında birer servis; faturalama yeniden başladığında hiçbiri. Elle kurulumda bu sayı elle uygulanır: kayıt deposunu yeniden başlatan kişi, geri kalan üçünü de kendisi başlatmak zorundadır ve hangilerinin gerektiği bağımlılık listesinden değil, hatalardan öğrenilir.
Ortamdan Çıkan ve Çıkmayan
Son üç satır bu dersin yalıtım hesabıdır. Elle kurulum altı komut, sekiz bayrak ve yirmi dört dizilişten birini seçme kararı ister; yirmi dördün yirmi ikisi en az bir bağımlılığı bozar. Bildirimsel biçimde bunların yerine yirmi altı alanlık bir dosya ve tek bir komut vardır, diziliş kararı sıfırdır. Ortamdan çıkarılan fark budur: başlatma sırası, ağ ve birim adları, bayrak kümesi ve yeniden başlatmanın yayılımı, kuran kişinin kabuk geçmişinden çıkıp çıktının yanındaki bir dosyaya girer. Bedel iki kalemdir: yazılan yirmi altı alan ve hazır olma denetiminin eklediği üç adım.
Bu sayıları okurken bir ayrım korunmalıdır: altı komut ile sekiz bayrak ortadan kalkmaz, yer değiştirir. Ağın kurulması, birimin oluşturulması ve dört sürecin başlatılması yine olur; yalnız bunları hangi sırayla ve hangi bayraklarla yapacağına karar veren, kabuğun başındaki kişi değil dosyadır. Kazanç komut sayısında değil, aynı kurulumu ikinci kez kuranın aynı yirmi iki yanlış dizilişten kaçınmasının kanıtlanabilir olmasındadır. Bileşim dosyası bir kurulumu kısaltmaz, tekrarlanabilir kılar; kısalan tek şey karar sayısıdır.
Yalıtımın delindiği yer aynı dosyada yazılıdır. Dört servis tek bir ağı paylaşır; bileşim
dosyası servisleri birbirine bağlamak için konteyner ağı dersinde kurulan ayrımı gevşetir,
birbirini görmesi gereken dördü aynı ad çözümleme uzayına koyar. kayit-verisi birimi iki servise
takılıdır — kayıt deposunda /veri, faturalamada /arsiv yolunda. Aynı bayt kümesi iki ayrı
konteynerde, iki ayrı kimlikle yazılabilir; önceki dersin izin matrisi burada tek bir ağaca değil,
iki ağacın kesiştiği bir noktaya bakar. Hazır olma denetiminin kendisi de bir kanıt değildir: bu modelde
hazır alanı bir sayıdır ve sayı yanlışsa yarışlar geri gelir, yalnız görünmez olurlar.
Özet
- Bileşim dosyasına bir şema tanımlamak, kurulum hatalarını başlatmadan önce görünür kılar: bozuk kurguda beş hatanın beşi tek okumada bulundu, elle kurulumda her biri ayrı bir koşum olurdu.
- Başlatma sırası grafikten türetilir; dört servisin yirmi dört dizilişinden yalnız ikisi geçerlidir ve döngü eklendiğinde sıralamanın olmadığı üç kenarlık çevrim adıyla basılır.
- Doğru sıra yarışları bitirmez: hazır olma denetimi olmadan dört bağımlılık kenarının üçü erken bağlanır, toplam beş adımlık gizli bekleme doğar.
- Hazır olma denetimi yarışı üçten sıfıra indirir, ayağa kalkmayı üç adımdan altı adıma çıkarır; takas dosyada yazılı bir seçim hâline gelir.
- Kayıt deposunun yeniden başlatılması üç servisi etkiler, faturalamanınki hiçbirini; elle kurulumda bu sayı hatalardan öğrenilir, dosyada bağımlılık listesinden okunur.
- Ortamdan çıkan fark altı komut, sekiz bayrak ve yirmi iki hatalı dizilişin dışlanmasıdır; bedeli yirmi altı alanlık dosya ile üç ek adımdır. Delik ortak ağ ile iki servise takılı tek birimdir.
Sonraki Adım
Bu ders bileşimi bir kez ayağa kaldırdı ve orada bıraktı. Oysa yerel ortamın asıl kullanımı tek bir başlatma değil, gün boyunca yüzlerce kez tekrarlanan bir döngüdür: bir satır değiştirilir, sonucu görülür, bir satır daha değiştirilir. O döngüde imajın yeniden üretilmesi ile kaynağın konteynere bağlanması iki ayrı biçimdir ve ikisi de bedava değildir. Sonraki ders bu iki biçimi değişiklik başına sayarak karşılaştırır — kopyalanan bayt, geçersizleşen katman ve geri bildirim adımı — ve ikinci biçimin ödediği bedeli adıyla ölçer: bağlanan kaynağın ve atlanan derleme adımının çalışan ortamı üretimden ne kadar uzaklaştırdığı, bu uzaklığın hangi hata sınıfını gizlediği.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.