Ders 05 / 16
Yük Dengeleyici Rolü
Aynı işi yapan kopyalar arasında isteği paylaştıran katmanın üç sorumluluğu: dağıtımın yerel süreçlerle ölçülmesi, bir kopya düştüğünde sağlık denetimi olan ve olmayan düzeneklerin yanıtlanan istek oranıyla karşılaştırılması, TLS sonlandırmanın konum kararı olarak ele alınması ve giriş kursunda hesaplanan tepe istek hızının kopya sayısına bölünmesi.
İçindekiler
Önceki konu isteklerin bir kısmını uygulamaya hiç ulaştırmamayı başardı: kenar önbelleği takip yanıtlarının onda dokuzunu sınırda karşıladı, ayrılan içerik yolu bir sayfa yüklemesinin isteklerinin neredeyse tamamını uygulamadan uzaklaştırdı ve alan adı katmanı isteğin hangi bölgeye gideceğini seçti. Geriye uygulamaya gerçekten ulaşması gereken istekler kaldı — uçta saniyede 513,89 tanesi. Bunlar için bir soru yanıtsızdır: bölge seçildikten sonra, aynı işi yapan birden çok kopya varken istek hangisine düşer, ve o kopya ayakta değilse bunu kim fark eder.
Bu ders o iki soruyu tek bir bileşene bağlar. Yük dengeleyici (load balancer), aynı işi yapan kopyaların önünde durup gelen isteği içlerinden birine yönlendiren aygıttır. Dersin işi bu aygıtın sorumluluklarını saymak, her birinin kod düzeyinde ölçülebilen sonucunu almak ve Sistem Tasarımına Giriş kursunda hesaplanan tepe istek hızının kopya sayısına bölünmesinin ne verdiğini göstermektir.
Üç Sorumluluk
Dengeleyicinin taşıdığı işler ayrı ayrı adlandırılabilir ve her biri ayrı bir karar gerektirir.
Dağıtım. Gelen isteğin hangi kopyaya gideceğinin seçilmesi. Seçimin kuralı bir sonraki derslerin konusudur; bu derste kural en yalın biçimiyle sıralı dağıtımdır — kopyalar sırayla seçilir.
Sağlık denetimi. Bir kopyanın hâlâ istek alabilir durumda olup olmadığının bağımsız olarak sınanması ve olmayanın havuzdan çıkarılması. Denetim isteği kullanıcının isteğinden ayrıdır: dengeleyici belirli aralıklarla kopyalara sorar, yanıt alamadığında o kopyayı dağıtımdan düşürür.
TLS sonlandırma. Şifreli bağlantının nerede biteceğinin seçilmesi. Bağlantı dengeleyicide biterse dengeleyici isteğin içeriğini görür; kopyaya kadar şifreli taşınırsa görmez.
Bu üç işin bir kısmını Sunucu Tarafı Temelleri kursunda kurulan ters vekil de yapar; ikisinin işlev örtüşmesi ve ayrımı bu konunun beşinci dersinin konusudur. Burada dengeleyici tek bir rolle ele alınır: aynı işi yapan kopyalar arasında paylaştırma.
Düzenek
Ölçüm yerel süreçlerle kurulur. Üç kopya birer node:http sunucusudur ve her biri yanıtına
kendi adını yazar; dengeleyici sıralı dağıtım yapar ve sağlık denetimi açık ya da kapalı
çalışabilir. Gerçek bir yük dengeleyici, bulut ortamı ya da kapsayıcı kurulmaz.
// denge/kopya.mjs — bir uygulama kopyasi: takip yanitini dondurur, kendi adini basliga yazar import { createServer } from "node:http"; const [port, ad] = [Number(process.argv[2]), process.argv[3]]; const GOVDE = (no) => JSON.stringify({ no, durum: "aktarma merkezinde", bolge: "35", adim: 4, rota: ["kabul", "cikis", "aktarma-34"], }); if (Number.isInteger(port) === false) console.log("kullanim: node denge/kopya.mjs <nokta> <ad>"); else createServer((istek, yanit) => { const url = new URL(istek.url, "http://yerel"); if (url.pathname === "/saglik") { yanit.writeHead(200, { "x-kopya": ad }).end("ayakta"); return; } const govde = GOVDE(url.searchParams.get("no") ?? "-"); yanit.writeHead(200, { "content-type": "application/json", "x-kopya": ad }); yanit.end(govde); }).listen(port, "127.0.0.1");
Dengeleyici üç işten ikisini yapıyor: sıraya göre bir kopya seçiyor ve kopyaların sağlığını ayrı bir zamanlayıcıda denetliyor. Denetim aralığı 200 milisaniye, düşmüş sayılma eşiği tek bir başarısız turdur; ikisi de bu konunun varsayımıdır ve aşağıda Y3 olarak anılır.
// denge/dengeleyici.mjs — sirali dagitim + saglik denetimi. Kullanim: // node denge/dengeleyici.mjs <nokta> <acik|kapali> <kopya-noktalari...> import { createServer, request, get } from "node:http"; const ARALIK = 200; // varsayim Y3: saglik denetimi araligi (ms) const ESIK = 1; // varsayim Y3: ust uste kac basarisizlik dusmus sayilir const nokta = Number(process.argv[2]); const saglikAcik = process.argv[3] === "acik"; const kopya = process.argv.slice(4).map((p) => ({ nokta: Number(p), ayakta: true, hata: 0 })); let sira = 0, dusen = 0; function denetle(k) { const r = get({ port: k.nokta, path: "/saglik", timeout: 100 }, (y) => { y.resume(); if (y.statusCode === 200) { k.hata = 0; k.ayakta = true; } else basarisiz(k); }); r.on("error", () => basarisiz(k)); r.on("timeout", () => r.destroy()); } function basarisiz(k) { k.hata += 1; if (k.hata >= ESIK) k.ayakta = false; } if (Number.isInteger(nokta) === false || kopya.length === 0) { console.log("kullanim: node denge/dengeleyici.mjs <nokta> <acik|kapali> <kopya-noktalari...>"); } else { if (saglikAcik) setInterval(() => kopya.forEach(denetle), ARALIK).unref(); createServer((istekDisi, yanitDisi) => { if (istekDisi.url === "/yonet/durum") { yanitDisi.writeHead(200, { "content-type": "application/json" }); yanitDisi.end(JSON.stringify({ ulasilamayan: dusen, ayakta: kopya.filter((k) => k.ayakta).map((k) => k.nokta) })); return; } const havuz = kopya.filter((k) => k.ayakta); if (havuz.length === 0) { yanitDisi.writeHead(503).end("havuz bos"); return; } const secilen = havuz[sira++ % havuz.length]; const istekIc = request({ port: secilen.nokta, path: istekDisi.url, method: istekDisi.method, headers: istekDisi.headers }, (yanitIc) => { yanitDisi.writeHead(yanitIc.statusCode, yanitIc.headers); yanitIc.pipe(yanitDisi); }); istekIc.on("error", () => { dusen += 1; yanitDisi.writeHead(502).end("kopyaya ulasilamadi"); }); istekDisi.pipe(istekIc); }).listen(nokta, "127.0.0.1"); }
İstemci istekleri sırayla gönderir; eşzamanlılık 1’de tutulması dağıtım sayılarını belirlenimli kılar, yoksa aynı düzenek her koşumda başka bir bölünme verir.
// denge/istemci.mjs — sirayla istek gonderir, yaniti hangi kopyanin verdigini sayar import { Agent, request } from "node:http"; const [nokta, adet, etiket] = [Number(process.argv[2]), Number(process.argv[3]), process.argv[4]]; const agent = new Agent({ keepAlive: true, maxSockets: 1 }); function bir(no) { return new Promise((coz, sap) => { const r = request({ port: nokta, path: `/takip?no=${no}`, agent }, (y) => { y.resume(); y.on("end", () => coz({ kod: y.statusCode, kopya: y.headers["x-kopya"] ?? "-" })); }); r.on("error", sap); r.end(); }); } if (Number.isInteger(nokta) === false) console.log("kullanim: node denge/istemci.mjs <nokta> <adet> <etiket>"); else { const sayac = new Map(); let basarili = 0, basarisiz = 0; for (let i = 0; i < adet; i += 1) { const s = await bir(`G${i}`); if (s.kod === 200) basarili += 1; else basarisiz += 1; const anahtar = s.kod === 200 ? s.kopya : `kod ${s.kod}`; sayac.set(anahtar, (sayac.get(anahtar) ?? 0) + 1); } const dagitim = [...sayac].sort().map(([a, n]) => `${a}=${n}`).join(" "); console.log(`${etiket.padEnd(30)} yanitlanan ${String(basarili).padStart(4)}/${adet}` + ` basarisiz ${String(basarisiz).padStart(3)} | ${dagitim}`); agent.destroy(); }
Sürücü betik aynı üç kopyanın önüne iki dengeleyici koyar: birinde sağlık denetimi açık,
ötekinde kapalı. Sonra k2 kopyası öldürülür ve aynı yük her iki dengeleyiciye gönderilir.
Bağlantı noktası numaraları ortama bağlıdır; makinede kullanımdaysa değiştirilmelidir.
# olc.sh — uc kopya, iki dengeleyici (saglik denetimi acik ve kapali); k2 kopyasi dusurulur node denge/kopya.mjs 8861 k1 & K1=$! node denge/kopya.mjs 8862 k2 & K2=$! node denge/kopya.mjs 8863 k3 & K3=$! node denge/dengeleyici.mjs 8851 acik 8861 8862 8863 & DA=$! node denge/dengeleyici.mjs 8852 kapali 8861 8862 8863 & DK=$! sleep 1 node denge/istemci.mjs 8851 300 "uc kopya ayakta" kill $K2; sleep 1 node denge/istemci.mjs 8851 300 "k2 dustu, denetim acik" node denge/istemci.mjs 8852 300 "k2 dustu, denetim kapali" echo "havuz (denetim acik) : $(curl -s localhost:8851/yonet/durum)" echo "havuz (denetim kapali): $(curl -s localhost:8852/yonet/durum)" kill $K1 $K3 $DA $DK
uc kopya ayakta yanitlanan 300/300 basarisiz 0 | k1=100 k2=100 k3=100
k2 dustu, denetim acik yanitlanan 300/300 basarisiz 0 | k1=150 k3=150
k2 dustu, denetim kapali yanitlanan 200/300 basarisiz 100 | k1=100 k3=100 kod 502=100
havuz (denetim acik) : {"ulasilamayan":0,"ayakta":[8861,8863]}
havuz (denetim kapali): {"ulasilamayan":100,"ayakta":[8861,8862,8863]}
Ölçülen Etki
Bu sayılar ölçüm sınıfındadır: yerel süreçlerden alınmıştır. Belirlenimlidir, çünkü istekler sırayla gönderilir ve dağıtım kuralı sıralıdır; başka bir makinede de aynı bölünme çıkar, ama bağlantı noktaları ortama bağlıdır.
İlk satır dağıtımı gösteriyor: üç kopya ayaktayken 300 istek 100–100–100 bölünüyor. Dengeleyici tek başına bir kazanç üretmiyor, yalnız yükü üçe ayırıyor — ve bu ayırma bir sonraki bölümde bir kapasite hesabına dönüşecek.
İkinci ve üçüncü satır dersin asıl ölçümüdür. Aynı arıza, aynı yük, aynı kopyalar. Sağlık
denetimi açık dengeleyici 300 isteğin 300’ünü yanıtladı ve yükü kalan iki kopyaya 150–150 böldü;
havuz durumu k2 kopyasının bağlantı noktasını listeden düşürmüş durumda. Sağlık denetimi kapalı
dengeleyici 300 isteğin yalnız 200’ünü yanıtladı: sıralı dağıtım her üç istekten birini ölü
kopyaya gönderdi ve 100 istek 502 ile döndü. Havuz durumu üç kopyanın da ayakta sayıldığını
gösteriyor.
Buradan çıkan sonuç dengeleyicinin tanımını değiştirir. Dağıtım tek başına hizmet erişilebilirliği üretmez. Kopya sayısını artırmak, düşen kopyayı fark eden bir denetim yoksa yalnızca arızayı seyrelten bir şeydir: bir kopya düştüğünde sistem tamamen durmaz, ama isteklerin üçte biri düşmeye devam eder ve bu durum kendini bir toplu kesinti olarak duyurmaz. Sağlık denetimi, kopyalamayı bir hizmet erişilebilirliği aracına çeviren parçadır.
TLS Sonlandırma Nerede Biter
Şifreli bağlantının nerede bittiği bir konum kararıdır ve bu kursta ölçülen sonucu bir
süre değil, bir görünürlüktür. Yukarıdaki dengeleyici istekDisi.url değerini okuyor —
/yonet/durum yolunu ayırt edebilmesi buna dayanıyor. Bu satırın var olabilmesinin koşulu
bağlantının dengeleyicide sonlanmasıdır: şifreli akış kopyaya kadar taşınıyorsa dengeleyicinin
elinde yol, başlık ve gövde değil yalnız baytlar olur. TLS ve el sıkışma Ağ Modelleri ve
Protokoller kursunda kuruldu ve burada yeniden anlatılmaz; kararın bu derse ait sonucu şudur:
sonlandırma, içeriğe bakan bir dağıtım kuralının önkoşuludur.
Kararın ikinci sonucu sayılabilir. Sertifikayı tutan uç sayısı, sonlandırma dengeleyicide yapıldığında 1, kopyalarda yapıldığında kopya sayısı kadardır; havuza bir kopya eklemek ilkinde 0 sertifika işi, ikincisinde 1 sertifika işi doğurur. Şifreleme işinin işlemci maliyeti burada ölçülmez, çünkü seçilen şifre takımına ve donanıma bağlıdır — bu kursun kuralı gereği ölçülmeyen bir sayı yazılmaz.
Hesaba Geri Dönüş
Sistem Tasarımına Giriş kursu uçta tepe istek hızını 513,89 istek/s olarak hesaplamıştı. O sayı tek bir kopyanın taşıyacağı yük değildir; kopya sayısına bölünür. Bölmenin yapılabilmesi için bu konunun kendi varsayımları gerekir ve bunlar K01’in V1–V13 tablosuna eklenmez:
Y1 — kopya başına doyum verimi: 400 istek/s. Gerekçe: K01’in Gecikme ve Verim dersinde çıplak bir uçta ölçülen üst sınır 4268 istek/s’ydi; gerçek takip ucu depoya bakar, seri hâle getirir ve yetkilendirme yapar, bu yüzden plan sayısı ölçülenin çok altında seçilir. Y2 — hedef doluluk 0,50. Gerekçe: aynı dersin kuyruk modelinde doluluk yüzde 50’de süreyi 1,4 katına, yüzde 90’da 7 katına çıkarıyordu. Y3 — sağlık denetimi aralığı 200 ms, eşik 1 tur. Gerekçe: düzenekte kullanılan değerler.
// denge/kapasite.mjs — K01 hesabinin kopya sayisina bolunmesi ve saglik denetiminin butceye etkisi const TEPE_UC = 513.89; // K01 Kabaca Buyukluk Hesabi: tepe ucta istek/s const GUNLUK_ISTEK = 12_000_000 + 2_800_000; // K01: gunluk takip sorgusu + durum olayi const DOYUM = 400; // varsayim Y1: kopya basina doyum verimi (istek/s) const HEDEF_DOLULUK = 0.5; // varsayim Y2: kopyanin hedef dolulugu const DENETIM_MS = 200; // varsayim Y3: saglik denetimi araligi, esik 1 tur const KURTARMA_DK = 10; // K01 Erisilebilirligin Sayisal Ifadesi: kurtarma suresi const ARIZA_AY = 2.82; // K01: ayda butceye sigan ariza sayisi const guvenli = DOYUM * HEDEF_DOLULUK; const enAz = Math.ceil(TEPE_UC / guvenli); console.log(`kopya basina guvenli hiz = ${guvenli} istek/s (${DOYUM} x ${HEDEF_DOLULUK})`); console.log(`en az kopya = ceil(${TEPE_UC} / ${guvenli}) = ${enAz}\n`); console.log("kopya kopya basina istek/s doluluk bir kopya dususunde istek/s o an doluluk"); for (const n of [enAz, enAz + 1, enAz + 2]) { const pay = TEPE_UC / n, kalan = TEPE_UC / (n - 1); console.log(`${String(n).padStart(5)} ${pay.toFixed(2).padStart(20)} ` + `${(pay / DOYUM).toFixed(3).padStart(7)} ${kalan.toFixed(2).padStart(27)} ` + `${(kalan / DOYUM).toFixed(3).padStart(12)}`); } const ortalama = GUNLUK_ISTEK / 86_400; console.log(`\nortalama uc hizi = ${ortalama.toFixed(2)} istek/s; tepe / ${enAz} kopya = ` + `${(TEPE_UC / enAz).toFixed(2)} istek/s (tepe carpani ${enAz} oldugu icin ayni sayi)`); const ayIstek = GUNLUK_ISTEK * 30; console.log(`\npencere dusen istek/ariza aylik dusen istek istek tabanli erisilebilirlik`); for (const [ad, sn] of [["saglik denetimi", DENETIM_MS / 1000], ["elle kurtarma", KURTARMA_DK * 60]]) { const bir = (TEPE_UC / enAz) * sn, aylik = bir * ARIZA_AY; console.log(`${ad.padEnd(18)} ${bir.toFixed(2).padStart(17)} ${aylik.toFixed(0).padStart(19)} ` + `%${(100 * (1 - aylik / ayIstek)).toFixed(5)}`); } const denetimHizi = (enAz * 1000) / DENETIM_MS; console.log(`\ndenetim yuku = ${denetimHizi} istek/s, tepe ucun %${((denetimHizi / TEPE_UC) * 100).toFixed(1)}'i`); console.log(`duyarlilik: Y1 iki katina cikarsa en az kopya = ${Math.ceil(TEPE_UC / (2 * DOYUM * HEDEF_DOLULUK))}, ` + `yarilanirsa = ${Math.ceil(TEPE_UC / (0.5 * DOYUM * HEDEF_DOLULUK))}`);
kopya basina guvenli hiz = 200 istek/s (400 x 0.5)
en az kopya = ceil(513.89 / 200) = 3
kopya kopya basina istek/s doluluk bir kopya dususunde istek/s o an doluluk
3 171.30 0.428 256.94 0.642
4 128.47 0.321 171.30 0.428
5 102.78 0.257 128.47 0.321
ortalama uc hizi = 171.30 istek/s; tepe / 3 kopya = 171.30 istek/s (tepe carpani 3 oldugu icin ayni sayi)
pencere dusen istek/ariza aylik dusen istek istek tabanli erisilebilirlik
saglik denetimi 34.26 97 %99.99998
elle kurtarma 102778.00 289834 %99.93472
denetim yuku = 15 istek/s, tepe ucun %2.9'i
duyarlilik: Y1 iki katina cikarsa en az kopya = 2, yarilanirsa = 6
Bu sayıların hepsi hesap sınıfındadır. En az kopya sayısı üçtür ve o üç kopyada her biri 171,30 istek/s taşır. İkinci tablonun ilk sütunu bir kopya düştüğü anda ne olduğunu gösteriyor: kalan iki kopya 256,94 istek/s’ye ve 0,642 doluluğa çıkar; üç kopya seçilen bir tasarım, tek bir arızada hedef doluluğunun üstüne taşar. Dört kopyada aynı durumda doluluk 0,428’de kalır — yani “en az kopya sayısı” ile “bir kopya düşse de hedefi tutan kopya sayısı” ayrı iki sayıdır ve aradaki fark bir kopyadır.
Ortalama satırı bir çakışmayı kaydediyor: tepenin üç kopyaya bölümü 171,30 istek/s ve sistemin ortalama uç hızı da 171,30 istek/s. İki sayı eşittir çünkü tepe çarpanı 3’tür ve kopya sayısı da 3 seçilmiştir; bu bir bağıntı değildir, varsayımların ürettiği bir çakışmadır. Kopya sayısı değiştiğinde eşitlik bozulur.
Son tablo sağlık denetiminin kesinti bütçesindeki karşılığıdır. K01’in Erişilebilirliğin Sayısal İfadesi dersi aylık 43,2 dakikalık bütçenin 28,2 dakikasını arızaya ayırmış, on dakikalık kurtarma süresiyle ayda 2,82 arıza saymıştı. O hesap her arızanın tam kesinti olduğunu varsayıyordu. Üç kopyalı bir havuzda tek bir kopyanın düşmesi tam kesinti değildir: sağlık denetimi varsa arıza başına 34,26 istek düşer ve aylık toplam 97 istektir. Denetim yoksa aynı arıza kurtarmaya kadar sürer, arıza başına 102 778 istek düşer ve aylık toplam 289 834’e çıkar — aradaki oran 2989 kattır. Denetimin faturası da hesaplanmıştır: kopyalara giden 15 istek/s, tepe uçtaki yükün yüzde 2,9’u.
İstek tabanlı sütun bir uyarı içeriyor. Denetimsiz düzenin oranı yüzde 99,93472’dir ve yüzde 99,9’luk hedefin üstünde görünür; oysa aynı arızalar zaman tabanlı bütçenin arıza payının tamamını harcar. Aynı arıza iki tabanda iki farklı büyüklükte görünüyor, çünkü isteklerin yalnızca üçte biri düşüyor. K01 bu iki tabanı ayırmıştı; buradaki sayılar ayrımın neden gerekli olduğunu gösteriyor.
Özet
- Yük dengeleyici üç sorumluluk taşır: dağıtım, sağlık denetimi ve TLS sonlandırma; ilki yükü paylaştırır, ikincisi düşen kopyayı havuzdan çıkarır, üçüncüsü şifreli bağlantının nerede bittiğini belirler.
- Aynı arızada sağlık denetimi açık düzenek 300/300 isteği yanıtladı ve yükü 150–150 böldü; kapalı düzenek 200/300 yanıtladı ve 100 istek 502 ile döndü.
- Dağıtım tek başına hizmet erişilebilirliği üretmez: düşen kopyayı fark eden bir denetim yoksa isteklerin üçte biri düşmeye devam eder ve bu bir toplu kesinti olarak görünmez.
- TLS sonlandırmanın bu dersteki ölçülen sonucu görünürlüktür: bağlantı dengeleyicide bitmezse yol ve başlık okunamaz, dolayısıyla içeriğe bakan bir dağıtım kuralı kurulamaz.
- Tepe 513,89 istek/s, kopya başına güvenli hız 200 istek/s (Y1 ve Y2) ile en az 3 kopya verir ve her kopya 171,30 istek/s taşır; bir kopya düştüğünde kalanlar 256,94 istek/s ve 0,642 dolulukla hedefin üstüne taşar, dört kopyada taşmaz.
- Sağlık denetimi arıza başına düşen isteği 102 778’den 34,26’ya indiriyor (aylık 289 834’e karşı 97, oran 2989 kat); denetimin kendi yükü 15 istek/s, tepe uçtaki yükün yüzde 2,9’u.
Sonraki Adım
Bu dersin dengeleyicisi kararını her istek için verdi ve kararı verebilmek için isteğin yolunu
okudu — /yonet/durum ayrımı buna dayanıyordu. Okumak bir seçimdir, zorunluluk değil. Bir
dengeleyici isteğin içine hiç bakmadan da çalışabilir: kararı bağlantı kurulurken bir kez verir,
sonra o bağlantıdan akan her şeyi aynı kopyaya geçirir. Bu daha az iş yapar ve daha az şey bilir.
Sonraki ders bu düzeyi kurar: bağlantı düzeyinde dağıtımın ne bilemediğini, kalıcı bağlantıların
dağıtımı nasıl bozduğunu ve bağlantı başına verilen bir kararın istek başına dağılımı hangi
sapmayla ürettiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.