İçeriğe geç
academia.sh

Ders 17 / 19

Hız Sınırlama ve Kısıtlama

Hız sınırlayıcının koddaki yerleşimi ve anahtarı: aynı sınırlayıcının ağ geçidinde 8 yol-erişim çiftinden 2'sini, servis içi ara katmanda 6'sını görmesi, 2 çiftin hiçbir yerleşimin dışında kalması ve tek bir 20 parametresinin anahtar seçimine göre 20 ile 100.000 arasında değişen bir tavana dönüşmesi.

İçindekiler

Önceki beş kalıbın öznesi hep çağıran taraftı: bu sistem başka bir servisi çağırdığında ne olacağı düzenlendi. Panelin isteğe bağlı alanları yedekle dolduruldu, hata fırlatan çağrı yerleri sayıldı ve kalıbın kapsamadığı iki uç bulundu. Hepsi aşağı akışa bakıyordu.

Yukarı akış hiç düzenlenmedi. Ödünç servisi yalnız çağırmıyor, çağrılıyor da ve kaç çağrı alacağına dair hiçbir sınırı yok. Gecelik bir toplu iş yanlış yazılmış bir döngüye girdiğinde, o servisin önündeki zaman aşımı, yeniden deneme ve devre kesici hiçbir işe yaramaz — hepsi zaten kabul edilmiş bir isteğin başına ne geleceğini düzenler. Sınır kabul tarafına konur ve adı hız sınırlamadır; reddetmek yerine geciktiren biçimine kısıtlama denir.

Sınırlayıcının sistem üzerindeki etkisi ve algoritmaların karşılaştırması — sabit pencere, kayan pencere, jeton kovası — Dayanıklılık ve Güvenilirlik kursunda ve hız sınırlayıcı tasarımına ayrılmış vaka çalışmasında ölçüldü. Burada algoritma sabit tutulur ve iki başka soru sorulur: sınırlayıcı kodun hangi katında durur ve sınır kime uygulanır.

Üç Kat, Bir Sınırlayıcı

Sınırlayıcı tek bir işlevdir; onu üç yere koymak mümkündür. Ağ geçidine konursa bütün servislerin önünde tek yerde durur. Servis içi bir ara katmana konursa yalnız o servisi korur ama o servisin her yolunu korur. Çağrı yerine konursa yalnız sarmalandığı çağrıyı düzenler — bu üçüncüsü giden kısıtlamadır ve kapsamı, bir önceki dersteki sarmalanan çağrı yeri ölçüsüyle aynı biçimde sayılır. İlk ikisi burada gerçekten kurulur.

// sinir/kat.mjs — sinirlayici ve onu barindirabilecek iki kat: ag gecidi ve servis ici ara katman.
import http from "node:http";

// Sabit pencere. Pencere DY12 geregi kosumdan uzun secildi; algoritma secimi burada olculmuyor.
export function sinirlayici(anahtar, sinir = 20, pencere = 60_000) {
  const kova = new Map();
  let bas = Date.now();
  const s = { gecen: 0, red: 0, uye: new Map(), gorulen: 0 };
  return { s, kova, izin(i) {
    if (Date.now() - bas >= pencere) { kova.clear(); bas = Date.now(); }
    s.gorulen += 1;
    const n = (kova.get(anahtar(i)) ?? 0) + 1;
    kova.set(anahtar(i), n);
    const t = s.uye.get(i.uye) ?? { gecen: 0, red: 0 };
    if (n <= sinir) { t.gecen += 1; s.gecen += 1; } else { t.red += 1; s.red += 1; }
    s.uye.set(i.uye, t);
    return n <= sinir;
  } };
}

const istek = (req) => ({ uc: req.url, istemci: req.headers["x-istemci"], uye: req.headers["x-uye"] });
const yaz = (res, kod, g) => { res.writeHead(kod, { "content-type": "application/json" }); res.end(JSON.stringify(g)); };

// Servis: ara katman verilirse butun yollarini kapsar, verilmezse hicbirini.
export function servis(yollar, araKatman) {
  return http.createServer((req, res) => {
    const i = istek(req);
    if (yollar.includes(i.uc) === false) return yaz(res, 404, { yol: i.uc });
    if (araKatman && araKatman.izin(i) === false) return yaz(res, 429, { sinir: 1 });
    yaz(res, 200, { yol: i.uc });
  });
}

// Ag gecidi: sinir yalnizca yazilmis onek esleserse uygulanir. Oteki onekler gecer.
export function agGecidi(limit, onek, hedef) {
  return http.createServer(async (req, res) => {
    const i = istek(req);
    if (i.uc.startsWith(onek) && limit.izin(i) === false) return yaz(res, 429, { sinir: 1 });
    const y = await fetch(`http://127.0.0.1:${hedef(i.uc)}${i.uc}`,
      { headers: { "x-istemci": i.istemci ?? "", "x-uye": i.uye ?? "" } });
    yaz(res, y.status, await y.json());
  });
}

export async function dinle(s) { await new Promise((r) => s.listen(0, "127.0.0.1", r)); return s.address().port; }
export async function gonder(port, uc, istemci, uye) {
  const y = await fetch(`http://127.0.0.1:${port}${uc}`, { headers: { "x-istemci": istemci, "x-uye": uye } });
  await y.json();
  return y.status;
}

Ölçüm iki bölümdür. Önce kapsam: dört yol, iki erişim yolu ve iki yerleşim kurulur; her istek için hangi sınırlayıcının o isteği gördüğü sayılır. Sonra anahtar: kapsamı geniş olan yerleşim sabit tutulur ve dört ayrı anahtar seçimi aynı istek akışında koşturulur.

// sinir/olcum.mjs — once kapsam (hangi yol hangi katmanda sayiliyor), sonra anahtar secimi.
import { sinirlayici, servis, agGecidi, dinle, gonder } from "./kat.mjs";

const ODUNC = ["/odunc/al", "/odunc/liste", "/odunc-toplu/liste"];
const KATALOG = ["/katalog/ara"];
const ONEK = "/odunc/";                      // ag gecidine yazilan tek onek

// Iki ayri duzenek: A'da sinir yalniz ag gecidinde, B'de yalniz servis ici ara katmanda.
const gwA = sinirlayici(() => "genel", 1e9);
const araB = sinirlayici(() => "genel", 1e9);
const kur = async (ara) => {
  const o = await dinle(servis(ODUNC, ara)), k = await dinle(servis(KATALOG, null));
  return { o, k, hedef: (u) => (u.startsWith("/katalog") ? k : o) };
};
const A = await kur(null), B = await kur(araB);
const gwPortA = await dinle(agGecidi(gwA, ONEK, A.hedef));
const gwPortB = await dinle(agGecidi(sinirlayici(() => "genel", 1e9), "/hicbiri/", B.hedef));

console.log("-- kapsam: bir istek hangi yerlesimde sayiliyor --");
console.log(`${"yol".padEnd(20)}${"erisim".padEnd(11)}${"ag gecidi".padStart(10)}${"ara katman".padStart(12)}`);
const kapsam = { gecit: 0, ara: 0, hicbiri: 0, toplam: 0 };
for (const yol of [...ODUNC, ...KATALOG]) {
  for (const erisim of ["ag gecidi", "dogrudan"]) {
    const g0 = gwA.s.gorulen, a0 = araB.s.gorulen;
    await gonder(erisim === "dogrudan" ? (yol.startsWith("/katalog") ? A.k : A.o) : gwPortA, yol, "olcum", "u0");
    await gonder(erisim === "dogrudan" ? (yol.startsWith("/katalog") ? B.k : B.o) : gwPortB, yol, "olcum", "u0");
    const g = gwA.s.gorulen > g0, a = araB.s.gorulen > a0;
    kapsam.toplam += 1; if (g) kapsam.gecit += 1; if (a) kapsam.ara += 1; if (!g && !a) kapsam.hicbiri += 1;
    console.log(`${yol.padEnd(20)}${erisim.padEnd(11)}${(g ? "sayildi" : "atlandi").padStart(10)}${(a ? "sayildi" : "atlandi").padStart(12)}`);
  }
}
console.log(`${kapsam.toplam} yol-erisim ciftinden ag gecidi ${kapsam.gecit}, ara katman ${kapsam.ara} tanesini goruyor; ` +
  `${kapsam.hicbiri} cift hicbir yerlesimin disinda\n`);

// DY13: gecelik toplu is once kesintisiz kosar, mobil istemci uyelerini sirayla esitler, web sonra gelir.
const AKIS = [["toplu", "u5", 40], ["mobil", "u1", 12], ["mobil", "u2", 8], ["mobil", "u3", 4], ["web", "u4", 6]];
const PLAN = [];
for (const [istemci, uye, n] of AKIS)
  for (let i = 0; i < n; i += 1) PLAN.push({ istemci, uye, uc: istemci === "mobil" ? ODUNC[i % 3] : ODUNC[1] });

const ANAHTAR = [["sabit", () => "genel"], ["istemci", (i) => i.istemci],
  ["uye", (i) => i.uye], ["istemci+uc", (i) => `${i.istemci} ${i.uc}`]];
const UYE = ["u1", "u2", "u3", "u4", "u5"], SINIR = 20, ETKIN_UYE = 5000;
console.log(`-- anahtar secimi: ${PLAN.length} istek, kova basina sinir ${SINIR}, yerlesim ara katman --`);
console.log(`${"anahtar".padEnd(12)}${"kova".padStart(5)}${"tavan".padStart(7)}${"gecen".padStart(7)}${"red".padStart(5)}` +
  UYE.map((u) => `${u} g/r`.padStart(11)).join(""));
for (const [ad, f] of ANAHTAR) {
  const lim = sinirlayici(f, SINIR);
  const port = await dinle(servis(ODUNC, lim));
  for (const p of PLAN) await gonder(port, p.uc, p.istemci, p.uye);
  console.log(`${ad.padEnd(12)}${String(lim.kova.size).padStart(5)}${String(lim.kova.size * SINIR).padStart(7)}` +
    `${String(lim.s.gecen).padStart(7)}${String(lim.s.red).padStart(5)}` +
    UYE.map((u) => `${lim.s.uye.get(u).gecen}/${lim.s.uye.get(u).red}`.padStart(11)).join(""));
}
console.log(`\nyazilan tek parametre ${SINIR}; gercek tavan kova sayisiyla carpilir. Anahtar uye secilirse kova ` +
  `sayisi uye sayisidir: DY14'un ${ETKIN_UYE} etkin uyesinde ayni yapilandirma ${ETKIN_UYE * SINIR} istek/pencere demektir.`);
process.exit(0);
-- kapsam: bir istek hangi yerlesimde sayiliyor --
yol                 erisim      ag gecidi  ara katman
/odunc/al           ag gecidi     sayildi     sayildi
/odunc/al           dogrudan      atlandi     sayildi
/odunc/liste        ag gecidi     sayildi     sayildi
/odunc/liste        dogrudan      atlandi     sayildi
/odunc-toplu/liste  ag gecidi     atlandi     sayildi
/odunc-toplu/liste  dogrudan      atlandi     sayildi
/katalog/ara        ag gecidi     atlandi     atlandi
/katalog/ara        dogrudan      atlandi     atlandi
8 yol-erisim ciftinden ag gecidi 2, ara katman 6 tanesini goruyor; 2 cift hicbir yerlesimin disinda

-- anahtar secimi: 70 istek, kova basina sinir 20, yerlesim ara katman --
anahtar      kova  tavan  gecen  red     u1 g/r     u2 g/r     u3 g/r     u4 g/r     u5 g/r
sabit           1     20     20   50       0/12        0/8        0/4        0/6      20/20
istemci         3     60     46   24       12/0        8/0        0/4        6/0      20/20
uye             5    100     50   20       12/0        8/0        4/0        6/0      20/20
istemci+uc      5    100     50   20       12/0        8/0        4/0        6/0      20/20

yazilan tek parametre 20; gercek tavan kova sayisiyla carpilir. Anahtar uye secilirse kova sayisi uye sayisidir: DY14'un 5000 etkin uyesinde ayni yapilandirma 100000 istek/pencere demektir.

Sınırın Dışında Kalan Yol

Ağ geçidindeki yerleşim sekiz yol-erişim çiftinden ikisini görüyor. Bu, konumun kendisiyle ilgili değil, ağ geçidine yazılan tek satırla ilgilidir: /odunc/ öneki. Ödünç servisinin üçüncü yolu /odunc-toplu/liste adını taşıyor ve bu ad öneke uymuyor. Yol sonradan eklendi, ağ geçidindeki önek listesi güncellenmedi ve sınırlayıcı o yolu hiç görmedi. Kod hiçbir yerde hata vermiyor; kural sessizce eksik kalıyor.

İkinci ve daha ağır boşluk erişim yolundadır. Ağ geçidi yalnız kendi üzerinden geçen isteği görebilir. Servisin kendi dinleyicisine doğrudan ulaşan her istek — başka bir iç servisin çağrısı, bir bakım betiği, bir sağlık yoklaması — sınırlayıcıya hiç uğramaz. Tablodaki dört dogrudan satırının dördünde de ağ geçidi sütunu atlandi yazıyor. Ağ geçidi bir sınır değil, bir kapıdır; kapının yanından geçilebiliyorsa sınır yoktur.

Servis içi ara katman altı çifti görüyor, çünkü işleyici tablosunun tamamını sarmalıyor: yolun adı ne olursa olsun, istek nereden gelirse gelsin, o servise ulaşan her istek sayılıyor. Bedeli ise kapsamın dar olmasıdır — yalnız o süreç. Katalog servisi ayrı bir süreçtir ve kendi ara katmanı yoktur; /katalog/ara iki erişim yolunda da hiçbir sınırlayıcının görüş alanına girmiyor. İki kat birlikte kurulsa bile bu iki çift açık kalır, çünkü boşluk katmanda değil, sınırlayıcının hiç kurulmadığı süreçtedir.

Kısıtlama biçimi bu tabloyu değiştirmez. Sınırlayıcı 429 döndürmek yerine isteği bir kuyruğa alıp geciktirse de, kuyruğa alabildiği istekler yine yalnız gördüğü isteklerdir. Reddetmek ile geciktirmek arasındaki seçim isteğin akıbetiyle ilgilidir; kapsam sorusu ondan bağımsızdır ve önce sorulur.

Anahtar Kimi Sınırlıyor

İkinci tablo aynı yerleşimi, aynı sınırı ve aynı yetmiş isteklik akışı dört ayrı anahtarla koşturuyor. Akış DY13’tür: gecelik toplu iş kırk istekle başlıyor, mobil istemci üç üyesini sırayla eşitliyor, web istemcisi en sonda geliyor.

Sabit anahtar — yani anahtar işlevinin her istek için aynı değeri döndürmesi — bütün istemcileri tek kovada topluyor. Toplu iş ilk yirmi isteğiyle kovayı bitiriyor ve geri kalan elli istek reddediliyor: dört üyenin dördü de sıfır geçen istekle kalıyor. Mobil istemcinin on iki isteği, web istemcisinin altı isteği, hiçbiri sisteme ulaşmıyor. Bu, sınırlayıcı olmayan bir sistemden daha kötüdür: sınırlayıcı olmasaydı toplu işin yükü servisi yavaşlatırdı, sınırlayıcı varken toplu iş öteki herkesi tamamen dışarıda bırakıyor. Yanlış anahtar, koruma aracını bir yayılma aracına çeviriyor.

İstemci anahtarı kovayı üçe bölüyor ve toplu işi kendi kotasına hapsediyor; geçen istek sayısı yirmiden kırk altıya çıkıyor. Ama mobil istemcinin arkasında üç ayrı üye var ve üçü aynı kovayı paylaşıyor. Mobil kotasını ilk iki üye tüketiyor, üçüncü üyenin dört isteğinin dördü de reddediliyor. Bu üye dört istek gönderdi ve hiçbiri geçmedi; sınırı aşan davranış başkasınındı. İstemci anahtarı paylaşılan bir istemcinin arkasındaki kullanıcıları birbirinin kotasından yedirir.

Üye anahtarı bunu düzeltiyor: mobil üç üyenin üçü de bütün isteklerini geçiriyor ve yalnız toplu iş sınırlanıyor. Ölçülen akışta istemci+uç anahtarı da aynı sonucu veriyor, çünkü mobil istemcinin yirmi dört isteği üç uca dağılıyor ve hiçbir kova sınıra ulaşmıyor.

Gerçek Tavan

Son iki sütun yapılandırma yüzeyinin en sinsi yanını gösteriyor. Yapılandırmada yazılan tek sayı 20. Sistemin gerçekte kabul ettiği üst sınır ise 20 × kova sayısı: sabit anahtarda 20, istemci anahtarında 60, üye anahtarında 100.

Kova sayısı bir yapılandırma değeri değildir; veriden gelir. Üye anahtarı seçildiğinde kova sayısı etkin üye sayısına eşittir. DY14 bu sistemde beş bin etkin üye varsayar; aynı 20 değeri o zaman pencere başına yüz bin istek demektir. Yapılandırma dosyasına bakan biri servisin yirmi istekle sınırlandığını sanır. Anahtar seçimi, sınır değerinin kendisinden daha belirleyicidir ve ikisi yapılandırmanın iki ayrı yerinde durduğu için aralarındaki çarpım hiçbir yerde yazılı değildir.

Bu, sınırlayıcının neden tek başına yeterli olmadığını da söylüyor. Üye başına sınır tek bir üyeyi frenler; servisin toplam kapasitesini korumaz. Toplam kapasiteyi koruyan sınır sabit anahtarlı olandır ve onun da ilk tabloda görülen bedeli vardır. İki anahtarın birlikte kurulması gerekir ve bu, iki ayrı kova kümesi, iki ayrı sınır değeri ve iki ayrı reddetme nedeni demektir.

Özet

  • Sınırlayıcının etkisi ve algoritma seçimi başka yerde ölçüldü; buradaki ölçü yerleşim ve anahtardır. Aynı sınırlayıcı işlevi ağ geçidine ve servis içi ara katmana kuruldu.
  • Ağ geçidi 8 yol-erişim çiftinden 2’sini, servis içi ara katman 6’sını görüyor. Fark iki nedenden gelir: ağ geçidine yazılan /odunc/ öneki sonradan eklenen /odunc-toplu/liste yolunu tutmuyor ve servise doğrudan ulaşan hiçbir istek ağ geçidinden geçmiyor.
  • 2 çift hiçbir yerleşimin dışındadır: katalog ayrı bir süreçtir ve orada sınırlayıcı hiç kurulmadı. Boşluk katman seçiminde değil, kalıbın kurulmadığı süreçtedir.
  • Sabit anahtar bütün istemcileri tek kovada topluyor: toplu iş yirmi isteğiyle kovayı bitiriyor ve öteki dört üyenin toplam otuz isteğinin hepsi reddediliyor. Bu sonuç sınırlayıcısız durumdan kötüdür.
  • İstemci anahtarı paylaşılan istemcinin arkasındaki üyeleri birbirinin kotasından yedirir: dört istek gönderen üye dördünde de reddedildi. Üye anahtarında aynı üye dört isteğini de geçirdi.
  • Yazılan tek parametre 20, gerçek tavan 20 × kova sayısı: sabit anahtarda 20, istemcide 60, üyede 100 ve DY14’ün beş bin üyesinde 100.000. Kova sayısı yapılandırmadan değil veriden gelir.

Sonraki Adım

Bu konuda altı kalıp kuruldu ve her biri belirli bir bozulma biçimine karşı yazıldı: yanıt gelmiyor, yanıt gecikiyor, bağımlılık sürekli düşüyor, kaynaklar tükeniyor, bir alan eksik, çağrı çok fazla. Her kalıbın kodda bir yeri, bir kapsamı ve o kapsamın dışında kalan çağrı yerleri var.

Kalıpların hangi bozulma biçimine karşılık geldiği ise hiçbir yerde tek tek yazılmadı. Bir bağımlılık bağlantıyı reddettiğinde, yavaş yanıt verdiğinde, boş gövde döndürdüğünde ya da geçerli görünen yanlış bir değer döndürdüğünde uygulama kodu bunları farklı biçimlerde görür — kimi bir hata nesnesi, kimi bir zaman aşımı, kimi hiçbir belirti. Sıradaki ders bu görünümleri tek tek üretir, her birinin çağrı yerinde nasıl göründüğünü yazar, hangi kalıbın hangisini yakaladığını eşleştirir ve geriye kalanı sayar: hiçbir kalıbın karşılamadığı bozulma biçimleri.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat