Ders 09 / 16
Yük Dengeleyici ve Ters Vekil
İki bileşenin aynı programla anlatılması ve ayrımın nerede olduğu: tek bir aktarma programının kural tablosuyla iki rolü de üstlenmesi, rollerin ayrı süreçlere bölünmesiyle tek süreçte toplanmasının durak zinciri ve durdurma etkisi üzerinden ölçülmesi, ayrımın sorumluluk ve konum olarak yazılması ve durak eklemenin giriş kursundaki bileşik erişilebilirlik hesabına faturasının çıkarılması.
İçindekiler
Önceki üç ders dengeleyiciyi bir yönlendirme aygıtı olarak kurdu: isteği alır, bir kural uygular, bir sürece iletir, yanıtı geri yazar. Aynı tanım Sunucu Tarafı Temelleri kursunda başka bir bileşen için de verilmişti. Ters vekil (reverse proxy) de isteği alır, bir kurala göre arka sunuculardan birine iletir ve yanıtı geri yazar. İki tanım birbirinden ayrılmıyor.
Bu ders örtüşmeyi kabul ederek başlar ve ayrımı başka bir yerde arar. İkisi de yeniden tanımlanmaz; ölçülen şey, iki rolün aynı süreçte toplanmasıyla ayrı süreçlere bölünmesinin ne değiştirdiğidir.
Aynı Program, İki Kural Tablosu
Örtüşmenin en kısa kanıtı tek bir programdır. Aşağıdaki aktarıcı bir kural tablosu alır: yol öneki, hedef listesi. Liste tek elemanlıysa yapılan iş yönlendirmedir — hangi bileşene. Listede aynı bileşenin birden çok kopyası varsa yapılan iş dağıtımdır — hangi kopyaya. İkisi aynı tabloda yan yana durabilir.
// vekil/aktarici.mjs — tek bir aktarma programi. Kural tablosu iki rolu de anlatir: // "/varlik=8891;*=8850" -> yola gore ayri bilesenlere yonlendirme (ters vekil) // "*=8892,8893,8894" -> ayni bilesenin kopyalari arasinda dagitim (yuk dengeleyici) // "/varlik=8891;*=8892,8893,8894" -> ikisi birden (birlesik surec) // Kullanim: node vekil/aktarici.mjs <nokta> <ad> <kural> import { createServer, request } from "node:http"; const [nokta, ad, kuralMetni] = [Number(process.argv[2]), process.argv[3], process.argv[4] ?? ""]; const KURAL = kuralMetni.split(";").filter(Boolean) .map((p) => { const [onek, liste] = p.split("="); return { onek, hedef: liste.split(",").map(Number), sira: 0 }; }); if (Number.isInteger(nokta) === false || KURAL.length === 0) { console.log("kullanim: node vekil/aktarici.mjs <nokta> <ad> <onek=hedef[,hedef];...>"); } else createServer((istekDisi, yanitDisi) => { yanitDisi.sendDate = false; const yol = new URL(istekDisi.url, "http://yerel").pathname; const k = KURAL.find((r) => r.onek !== "*" && yol.startsWith(r.onek)) ?? KURAL.find((r) => r.onek === "*"); if (k === undefined) { yanitDisi.writeHead(404).end("kural yok"); return; } const hedef = k.hedef[k.sira++ % k.hedef.length]; const onceki = istekDisi.headers["x-durak"]; const istekIc = request({ port: hedef, path: istekDisi.url, method: istekDisi.method, headers: { ...istekDisi.headers, "x-durak": onceki === undefined ? ad : `${onceki} > ${ad}` } }, (yanitIc) => { yanitDisi.writeHead(yanitIc.statusCode, yanitIc.headers); yanitIc.pipe(yanitDisi); }); istekIc.on("error", () => yanitDisi.writeHead(502, { "x-durak": `${ad} > ulasilamadi` }).end("yok")); istekDisi.pipe(istekIc); }).listen(nokta, "127.0.0.1");
Zincirin sonundaki süreçler yalnız adlarını bildirir; x-durak başlığı istek boyunca birikerek
isteğin dokunduğu durakları taşır.
// vekil/uc.mjs — zincirin sonundaki surec: adini x-durak basligina ekler import { createServer } from "node:http"; const [port, ad] = [Number(process.argv[2]), process.argv[3]]; if (Number.isInteger(port) === false) console.log("kullanim: node vekil/uc.mjs <nokta> <ad>"); else createServer((istek, yanit) => { yanit.sendDate = false; const onceki = istek.headers["x-durak"]; yanit.writeHead(200, { "content-type": "application/json", "x-durak": onceki === undefined ? ad : `${onceki} > ${ad}` }); yanit.end(JSON.stringify({ durum: "aktarma merkezinde", bolge: "35" })); }).listen(port, "127.0.0.1");
// vekil/istemci.mjs — iki yolu da dener, dokunulan durak zincirini ve yanitlanan istegi yazar import { request } from "node:http"; const [nokta, etiket] = [Number(process.argv[2]), process.argv[3] ?? "-"]; const YOL = ["/varlik/stil.css", "/takip"]; const ADET = 6; function bir(yol) { return new Promise((coz) => { const r = request({ port: nokta, path: yol }, (y) => { y.resume(); y.on("end", () => coz({ kod: y.statusCode, durak: y.headers["x-durak"] ?? "-" })); }); r.on("error", () => coz({ kod: 0, durak: "baglanti yok" })); r.end(); }); } if (Number.isInteger(nokta) === false) console.log("kullanim: node vekil/istemci.mjs <nokta> <etiket>"); else for (const yol of YOL) { const zincir = new Map(); let yanitlanan = 0; for (let i = 0; i < ADET; i += 1) { const s = await bir(yol); if (s.kod === 200) yanitlanan += 1; zincir.set(s.durak, (zincir.get(s.durak) ?? 0) + 1); } const d = [...zincir].sort().map(([a, n]) => `${a} (${n})`).join(" | "); console.log(`${etiket.padEnd(22)} ${yol.padEnd(17)} ${String(yanitlanan)}/${ADET} ${d}`); }
Düzenek aynı işi iki yerleşimle koşar. Ayrı yerleşimde iki aktarıcı süreci vardır: tv
içerik yolu ile uygulama yolunu ayırır, yd kopyalar arasında dağıtır. Birleşik yerleşimde
tek bir tvyd süreci ikisini de yapar. Her yerleşimde kopya listesini tutan süreç durdurulur ve
hangi yolun ayakta kaldığı sayılır.
# olc.sh — ayni is iki yerlesimle: iki ayri aktarici sureci, sonra tek birlesik surec. # Ikinci asamada kopya listesini tutan surec durduruluyor ve hangi yolun ayakta kaldigi sayiliyor. node vekil/uc.mjs 8891 statik & S=$! node vekil/uc.mjs 8892 k1 & K1=$! node vekil/uc.mjs 8893 k2 & K2=$! node vekil/uc.mjs 8894 k3 & K3=$! node vekil/aktarici.mjs 8860 tv "/varlik=8891;*=8850" & TV=$! node vekil/aktarici.mjs 8850 yd "*=8892,8893,8894" & YD=$! sleep 1 node vekil/istemci.mjs 8860 "ayri: tv + yd" kill $YD; sleep 0.5 node vekil/istemci.mjs 8860 "ayri: yd durduruldu" kill $TV; sleep 0.3 node vekil/aktarici.mjs 8861 tvyd "/varlik=8891;*=8892,8893,8894" & B=$! sleep 1 node vekil/istemci.mjs 8861 "birlesik: tvyd" kill $B; sleep 0.5 node vekil/istemci.mjs 8861 "birlesik: durduruldu" kill $S $K1 $K2 $K3
ayri: tv + yd /varlik/stil.css 6/6 tv > statik (6) ayri: tv + yd /takip 6/6 tv > yd > k1 (2) | tv > yd > k2 (2) | tv > yd > k3 (2) ayri: yd durduruldu /varlik/stil.css 6/6 tv > statik (6) ayri: yd durduruldu /takip 0/6 tv > ulasilamadi (6) birlesik: tvyd /varlik/stil.css 6/6 tvyd > statik (6) birlesik: tvyd /takip 6/6 tvyd > k1 (2) | tvyd > k2 (2) | tvyd > k3 (2) birlesik: durduruldu /varlik/stil.css 0/6 baglanti yok (6) birlesik: durduruldu /takip 0/6 baglanti yok (6)
İki Sayı, İki Yön
Bu sayılar ölçüm sınıfındadır ve belirlenimlidir. İki yerleşim iki ayrı sütunda ters sıralanıyor.
Durak sayısı. Ayrı yerleşimde takip isteği üç durağa dokunuyor: tv > yd > k1. Birleşik
yerleşimde iki durağa: tvyd > k1. İçerik yolu ikisinde de iki duraktır. Her durak bir ağ
atlaması, bir ayrıştırma ve bir yeniden yazma demektir; ayrı yerleşim aynı isteği bir kez fazla
elden geçiriyor.
Yalıtım. Kopya listesini tutan süreç durdurulduğunda ayrı yerleşimde içerik yolu 6/6
yanıtlanmaya devam ediyor, yalnız takip yolu 0/6’ya düşüyor — ve düşüş bile tv üzerinden bir 502
olarak dönüyor, yani istemci bir yanıt alıyor. Birleşik yerleşimde ikisi de 0/6 ve istemci hiçbir
yanıt alamıyor, çünkü bağlanacağı bir bağlantı noktası kalmıyor. Kopya listesinin değişmesi bir
yapılandırma değişikliğidir ve sık olur; birleşik süreçte o değişiklik içerik yolunu da
durdurur.
İki sayı aynı kararın iki yüzüdür: bölmek bir durak ekler, birleştirmek bir arıza yüzeyi ekler.
Ayrım Nerede
Aynı program iki rolü de üstlenebildiğine göre ayrım programda değildir. İki yerdedir.
Sorumluluk. Ters vekil “hangi bileşen” sorusunu yanıtlar: /varlik ile başlayan yol içerik
sürecine, geri kalanı uygulamaya. Kural tablosundaki önekler farklı işler yapan bileşenlere
işaret eder ve hedef listeleri tek elemanlıdır. Yük dengeleyici “hangi kopya” sorusunu yanıtlar:
hedef listesi aynı işi yapan süreçlerden oluşur ve içlerinden hangisinin seçildiği isteğin
sonucunu değiştirmez. İlk soruda yanlış seçim yanlış davranıştır, ikincisinde yalnız farklı
bir kopyadır.
Konum. Ters vekil uygulama sınırında durur: sistemin dış yüzünü tek bir yol uzayı olarak
gösterir ve arkasındaki bileşen bölünmesini gizler. Yük dengeleyici kopya sınırında durur: bir
bileşenin kaç kopyayla çalıştığını gizler. İki gizleme farklı ölçeklerde işler ve bu yüzden ikisi
zincirde farklı yerlerde durabilir — ölçümdeki tv > yd sırası bunun ifadesidir, tersi
anlamsızdır.
Bir sürecin ikisini birden üstlenmesi bu ayrımı ortadan kaldırmaz, yalnız aynı yapılandırma dosyasına yazar. Ayrımı görünür tutan şey kural tablosunun biçimidir: hangi satırın öneki bir bileşen ayrımı, hangisinin hedef listesi bir kopya kümesidir.
Hesaba Geri Dönüş
Bölmenin faturası durak sayısında görünüyordu; o sayının hizmet erişilebilirliğindeki karşılığı K01’de kurulmuştu. Erişilebilirliğin Sayısal İfadesi dersi takip yolunu dört bileşenli seri bir zincir olarak almış, bileşen başına yüzde 99,9 ile bileşik erişilebilirliği yüzde 99,6006 ve aylık kesintiyi 172,5 dakika bulmuştu. Aktarıcı süreçler o zincire eklenen yeni bileşenlerdir.
// vekil/zincir.mjs — durak eklemenin K01 bilesik erisilebilirlik hesabindaki karsiligi const AY = 30 * 24 * 60; // dakika const TABAN = 4; // K01 Erisilebilirligin Sayisal Ifadesi: dort bilesenli takip yolu const BILESEN = 0.999; // K01 varsayim: bilesen basina hizmet erisilebilirligi const seri = (a, n) => a ** n; const kopyali = (a, k) => 1 - (1 - a) ** k; const dakika = (a) => (1 - a) * AY; console.log("yerlesim durak zincir bileseni bilesik erisilebilirlik aylik kesinti tabana gore"); const taban = seri(BILESEN, TABAN); for (const [ad, durak, kopya] of [ ["durak yok (K01)", 0, 1], ["birlesik surec", 1, 1], ["ayri iki surec", 2, 1], ["ayri, her biri 2 kopya", 2, 2], ["birlesik, 2 kopya", 1, 2]]) { const a = taban * seri(kopyali(BILESEN, kopya), durak); console.log(`${ad.padEnd(22)}${String(durak).padStart(6)}${String(TABAN + durak).padStart(17)} ` + `${(a * 100).toFixed(4).padStart(22)}% ${dakika(a).toFixed(1).padStart(11)} dk ` + `${(dakika(a) - dakika(taban)).toFixed(1).padStart(9)} dk`); } const birlesik = taban * seri(BILESEN, 1), ayri = taban * seri(BILESEN, 2); console.log(`\nrolleri iki surece bolmenin faturasi = ${(dakika(ayri) - dakika(birlesik)).toFixed(1)} dk/ay`); console.log(`ayni fatura duraklar ikiser kopyali oldugunda = ` + `${(dakika(taban * seri(kopyali(BILESEN, 2), 2)) - dakika(taban * seri(kopyali(BILESEN, 2), 1))).toFixed(2)} dk/ay`); console.log(`K01 aylik kesinti butcesi (%99.9) = ${((1 - 0.999) * AY).toFixed(1)} dk; ` + `ayri yerlesimin kesintisi butcenin ${(dakika(ayri) / ((1 - 0.999) * AY)).toFixed(2)} kati`);
yerlesim durak zincir bileseni bilesik erisilebilirlik aylik kesinti tabana gore durak yok (K01) 0 4 99.6006% 172.5 dk 0.0 dk birlesik surec 1 5 99.5010% 215.6 dk 43.0 dk ayri iki surec 2 6 99.4015% 258.6 dk 86.0 dk ayri, her biri 2 kopya 2 6 99.6004% 172.6 dk 0.1 dk birlesik, 2 kopya 1 5 99.6005% 172.6 dk 0.0 dk rolleri iki surece bolmenin faturasi = 43.0 dk/ay ayni fatura duraklar ikiser kopyali oldugunda = 0.04 dk/ay K01 aylik kesinti butcesi (%99.9) = 43.2 dk; ayri yerlesimin kesintisi butcenin 5.99 kati
Bu sayılar hesap sınıfındadır. İlk üç satır bölmenin faturasını veriyor: tek kopyalı duraklarla, rolleri iki sürece ayırmak aylık kesintiyi 215,6 dakikadan 258,6 dakikaya çıkarıyor — ayda 43,0 dakika. Sayı K01’in yüzde 99,9 hedefi için ayırdığı bütün aylık bütçeye (43,2 dakika) neredeyse eşittir. Yani rolleri ayırma kararı, tek başına, bir aylık kesinti bütçesi kadar tutuyor.
Dördüncü ve beşinci satır faturanın nereden geldiğini gösteriyor. Duraklar ikişer kopyalı olduğunda aynı bölme 0,04 dakikaya iniyor ve iki yerleşim ayırt edilemez hâle geliyor. K01’in kopya tablosundaki kural burada da geçerli: koşut kopyalarda kesinti payı üs alır. Durak eklemenin faturası durak eklemekten değil, durağı tek kopyalı bırakmaktan geliyor.
Son satır tabloyu bir ölçütle bitiriyor. Ayrı yerleşimin aylık kesintisi 258,6 dakikadır ve yüzde 99,9 bütçesinin 5,99 katıdır. Bu, yerleşimin kötü olduğunu değil, yüzde 99,9 hedefinin bileşen başına yüzde 99,9 ile karşılanamayacağını söyler; K01 aynı sonucu dört bileşenle bulmuştu, iki durak eklemek onu keskinleştiriyor. Bir kenar katmanı tasarlanırken durak sayısı bir mimari tercih değil, doğrudan bir erişilebilirlik kalemidir.
Özet
- Yük dengeleyici ile ters vekil aynı programla yazılabilir: kural tablosunda hedef listesi tek elemanlıysa yapılan iş yönlendirme, aynı bileşenin kopyalarından oluşuyorsa dağıtımdır.
- Ayrı yerleşimde takip isteği üç durağa dokundu (
tv > yd > k1), birleşik yerleşimde ikiye (tvyd > k1); bölmek isteği bir kez fazla elden geçiriyor. - Kopya listesini tutan süreç durdurulduğunda ayrı yerleşimde içerik yolu 6/6 yanıtlanmaya devam etti, birleşik yerleşimde iki yol da 0/6 oldu ve istemci hiç yanıt alamadı.
- Ayrım sorumluluktadır ve konumdadır: ters vekil “hangi bileşen” sorusunu uygulama sınırında, yük dengeleyici “hangi kopya” sorusunu kopya sınırında yanıtlar; ilkinde yanlış seçim yanlış davranış, ikincisinde yalnız farklı bir kopyadır.
- Tek kopyalı duraklarla rolleri ayırmak aylık kesintiyi 215,6’dan 258,6 dakikaya çıkarıyor: ayda 43,0 dakika, yüzde 99,9 hedefinin bütün aylık bütçesi kadar.
- Duraklar ikişer kopyalı olduğunda aynı fatura 0,04 dakikaya iniyor; durak eklemenin bedeli durağı tek kopyalı bırakmaktan gelir.
Sonraki Adım
Buraya kadarki beş ders bir şeyi hiç sorgulamadı: dengeleyicinin seçimi serbesttir. Sıralı kural, en az bağlantı kuralı ve iki yerleşimin hepsi, bir isteğin herhangi bir kopyaya gönderilebileceğini varsaydı; tutarlı karma bile seçimi kararlı tutmayı isterken zorunlu kılmıyordu. Kopyaların birbirinin eşi olmaktan çıktığı bir durum vardır: bir istemcinin önceki isteğinden kalan bir durum belirli bir kopyanın belleğinde durursa, o istemcinin sonraki isteği artık herhangi bir kopyaya gidemez. Seçim bir tercih olmaktan çıkıp bir kısıt hâline gelir. Sonraki ders bu kısıtı adlandırır ve ölçer: dağılımın ne kadar bozulduğunu, bir kopya düştüğünde ne kadar durumun kaybolduğunu ve kopya eklemenin tepe yükü neden düşürmediğini sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.