İçeriğe geç
academia.sh

Ders 01 / 15

Durumsuz Servisler

Durumun servis sürecinin dışına taşınmasının yatay ölçeklemeye etkisi: aynı yükün iki kipte yerel süreçlerle ölçülmesi, kopya belleğinde tutulan oturumun kopyalar arasında bölünmesi ve yeniden başlatmada kaybolması, trafik katmanında ölçülen kopya sayısının geri alınması ve durumun dışarı taşınmasının durum deposuna ve gövde baytına ödettiği bedelin hesaplanması.

İçindekiler

Trafik Katmanı kursu isteği uygulamanın kapısına kadar getirdi: adı çözüldü, kenar önbelleğinden geçti, bir kopyaya düştü, ağ geçidinden ve kapıdan geçti. Kapının arkasında ise tasarlanmamış bir alan duruyor. Servislerin durumsuz olup olmadığı, birbirlerinin adresini nasıl buldukları, bir çağrı yanıt vermediğinde ne kadar bekleyip kaç kez deneyecekleri ve sözleşmelerinin nasıl değişeceği o kursta sessizce varsayıldı. Bu kurs o varsayımları açar.

Sıra en alttakinden başlar: servisin kendi belleğinde ne tuttuğu. Önceki kursun son ölçümü bunun neden ilk soru olduğunu göstermişti — oturum belirli bir kopyanın belleğinde durduğunda dengeleyicinin isteği dağıtma yetkisi kalmıyor, 0,50 doluluk hedefi için gereken kopya sayısı 3’ten 4’e çıkıyor ve eklenen kopyalar en yüklü kopyanın yükünü düşürmüyordu. Bu ders o bağın kaynağını ele alır. Durumsuz servis (stateless service), iki isteği birbirine bağlayan hiçbir veriyi kendi süreç belleğinde tutmayan servistir. Durumsuz süreç ve on iki etmen ilkesi Sunucu Tarafı Temelleri kursunda kuruldu; burada tanım tekrarlanmaz, ölçülür.

Hangi Durum Taşınır

Bir süreçte duran her veri taşınmak zorunda değildir. Ayrımı yapan soru şudur: bu veri kaybolduğunda ya da başka bir kopya bu veriyi göremediğinde yanıt değişir mi.

İki isteği birbirine bağlayan veri — oturum, çok adımlı bir işin ara sonucu, sayfalama imleci, tekillik anahtarı kaydı — taşınmak zorundadır; kaybolduğunda yanıt değişir. Yalnız hız için tutulan veri — sıcak kayıt önbelleği, açık bağlantı havuzu, derlenmiş yapılandırma — kalabilir; kaybolduğunda yanıt aynıdır, yalnız daha yavaş gelir. Bu ayrım bir kopyanın yeniden başlatılmasının ne anlama geldiğini de belirler: ilk kümede yeniden başlatma bir veri kaybıdır, ikincisinde bir ısınma süresidir.

Ölçüm birinci kümeyi ele alır. Gönderi takip servisinde bir satıcı entegrasyonu oturum açar ve art arda takip sorguları gönderir; servis o oturumda hangi gönderilerin izlendiğini tutar.

Düzenek

Ölçüm yerel süreçlerle kurulur. İki kopya aynı işi yapar ve tek parametreyle iki kipte çalışır: bellek kipinde oturum listesi kopyanın kendi belleğindedir, disari kipinde ayrı bir durum deposu sürecindedir. Gerçek bir küme, bulut ortamı ya da oturum deposu ürünü kurulmaz.

// durum/depo.mjs — oturum listesini surec disinda tutan durum deposu (tek surec, bellek ici)
import { createServer } from "node:http";

const oturumlar = new Map();
const nokta = Number(process.argv[2]);

if (Number.isInteger(nokta) === false) console.log("kullanim: node durum/depo.mjs <nokta>");
else createServer((istek, yanit) => {
  const ad = new URL(istek.url, "http://yerel").pathname.split("/").pop();
  if (istek.method === "PUT") {
    let m = "";
    istek.on("data", (p) => (m += p));
    istek.on("end", () => { oturumlar.set(ad, JSON.parse(m)); yanit.writeHead(204).end(); });
    return;
  }
  yanit.writeHead(200, { "content-type": "application/json" });
  yanit.end(JSON.stringify(oturumlar.get(ad) ?? null));
}).listen(nokta, "127.0.0.1");
// durum/kopya.mjs — takip servisi kopyasi; oturum listesi bellekte ya da durum deposunda durur.
// Kullanim: node durum/kopya.mjs <nokta> <ad> <bellek|disari> <depo-noktasi>
import { createServer } from "node:http";

const [nokta, ad, kip, depo] = process.argv.slice(2);
const yerel = new Map();

const oku = async (o) => (kip === "bellek" ? yerel.get(o) ?? null
  : (await fetch(`http://127.0.0.1:${depo}/oturum/${o}`)).json());
const yaz = async (o, liste) => {
  if (kip === "bellek") { yerel.set(o, liste); return; }
  await fetch(`http://127.0.0.1:${depo}/oturum/${o}`, { method: "PUT", body: JSON.stringify(liste) });
};

if (Number.isInteger(Number(nokta)) === false) console.log("kullanim: node durum/kopya.mjs <nokta> <ad> <bellek|disari> <depo-noktasi>");
else createServer(async (istek, yanit) => {
  const u = new URL(istek.url, "http://yerel");
  const oturum = u.searchParams.get("oturum") ?? "-";
  const json = (kod, govde) => yanit.writeHead(kod, { "content-type": "application/json" }).end(JSON.stringify({ ad, ...govde }));
  if (u.pathname === "/oturum") { await yaz(oturum, []); return json(201, { izlenen: 0 }); }
  const liste = await oku(oturum);
  if (liste === null) return json(409, { hata: "oturum yok" });
  if (u.pathname === "/takip") { liste.push(u.searchParams.get("no")); await yaz(oturum, liste); }
  json(200, { izlenen: liste.length });
}).listen(Number(nokta), "127.0.0.1");

İstemci oturumu ilk kopyada açar, sonra sekiz takip sorgusunu kopyalara sırayla dağıtır. Sıralı gönderim dağılımı belirlenimli kılar; dağıtım kuralı Trafik Katmanı kursunun konusuydu ve burada en yalın biçimiyle alınmıştır.

// durum/istemci.mjs — oturumu ilk kopyada acar, sekiz istegi kopyalara sirayla dagitir, sonra ozet alir
// Kullanim: node durum/istemci.mjs <etiket> <nokta...>
const [etiket, ...nokta] = process.argv.slice(2);
const al = async (n, yol) => {
  const y = await fetch(`http://127.0.0.1:${n}${yol}`);
  return { kod: y.status, ...(await y.json()) };
};

if (nokta.length === 0) console.log("kullanim: node durum/istemci.mjs <etiket> <nokta...>");
else {
  await al(nokta[0], "/oturum?oturum=S1");
  let bulan = 0;
  for (let i = 0; i < 8; i += 1) {
    const s = await al(nokta[i % nokta.length], `/takip?oturum=S1&no=TR-${4820 + i}`);
    if (s.kod === 200) bulan += 1;
  }
  const ozet = [];
  for (const n of nokta) {
    const s = await al(n, "/izleme?oturum=S1");
    ozet.push(`${s.ad}=${s.kod === 200 ? s.izlenen : s.hata}`);
  }
  console.log(`${etiket.padEnd(24)} oturumu bulan ${bulan}/8   ozet ${ozet.join("  ")}`);
}

Sürücü betik aynı yükü iki kipte koşturur ve her kipte bir kopyayı yeniden başlatır. Bağlantı noktası numaraları ortama bağlıdır; makinede kullanımdaysa değiştirilmelidir.

# olc.sh — ayni yuk iki kipte: durum kopyanin belleginde ve durum deposunda
node durum/depo.mjs 8891 & D=$!
node durum/kopya.mjs 8881 k1 bellek 8891 & A=$!
node durum/kopya.mjs 8882 k2 bellek 8891 & B=$!
sleep 1
node durum/istemci.mjs "bellekte" 8881 8882
kill $A; sleep 1
node durum/kopya.mjs 8881 k1 bellek 8891 & A=$!
sleep 1
echo "bellekte,  k1 yeniden baslatildi -> $(curl -s '127.0.0.1:8881/izleme?oturum=S1')"
kill $A $B; sleep 1

node durum/kopya.mjs 8881 k1 disari 8891 & A=$!
node durum/kopya.mjs 8882 k2 disari 8891 & B=$!
sleep 1
node durum/istemci.mjs "durum deposunda" 8881 8882
kill $A; sleep 1
node durum/kopya.mjs 8881 k1 disari 8891 & A=$!
sleep 1
echo "deposunda, k1 yeniden baslatildi -> $(curl -s '127.0.0.1:8881/izleme?oturum=S1')"
kill $A $B $D
bellekte                 oturumu bulan 4/8   ozet k1=4  k2=oturum yok
bellekte,  k1 yeniden baslatildi -> {"ad":"k1","hata":"oturum yok"}
durum deposunda          oturumu bulan 8/8   ozet k1=8  k2=8
deposunda, k1 yeniden baslatildi -> {"ad":"k1","izlenen":8}

Ölçülen Etki

Bu sayılar ölçüm sınıfındadır ve yerel süreçlerden alınmıştır; belirlenimlidir, çünkü istekler sırayla gönderilir. Bağlantı noktaları ortama bağlıdır.

Birinci satır bağın kendisini gösteriyor: bellek kipinde sekiz isteğin yalnız dördü oturumu buluyor. Bulanların hepsi oturumu açan kopyaya düşen isteklerdir; ötekiler oturum yok alıyor. Özet satırı bunun sonucunu taşıyor — k1 dört numara izliyor, k2 oturumu hiç görmüyor. Yani liste kaybolmuş değil, bölünmüş. Bölünen listeyi hiçbir kopya doğru veremez.

İkinci satır ikinci maliyeti veriyor: k1 yeniden başlatıldığında dört numaralı liste de gitti. Bir kopyanın yeniden başlatılması burada yayın, ölçek değişikliği ya da arıza sonrası kurtarma demektir; üçü de olağan işlerdir ve üçünde de veri kaybı doğuyor.

Üçüncü ve dördüncü satırlar aynı yükü disari kipinde veriyor. Sekiz isteğin sekizi oturumu buluyor, iki kopya da aynı sekiz numarayı sayıyor ve yeniden başlatılan kopya listeyi eksiksiz okuyor. Kopyalar birbirinin eşi hâline geldi: hangi kopyanın yanıtladığı artık yanıtı değiştirmiyor.

Ölçümün gösterdiği şey bir başarım kazancı değildir — disari kipi daha çok iş yapar. Gösterdiği şey bir koşuldur: yükün kopyalara serbestçe bölünebilmesi, iki isteği birbirine bağlayan verinin süreç dışında olmasına bağlıdır.

Hesaba Geri Dönüş

Koşulun sağlanmasının karşılığı Trafik Katmanı kursunun son dersinde sayıya dökülmüştü. O ders yapışkan düzende dengesizliği ölçmüş, 0,50 doluluk hedefi için gereken kopya sayısını 4 bulmuş ve en büyük oturumun payını bir seri pay olarak yazmıştı. Durumsuz düzende bu sayılar geri alınır.

// durum/kapasite.mjs — durumsuzlugun kopya sayisina etkisi ve durumun disariya tasima maliyeti
const TEPE_UC = 513.89;              // K01 Kabaca Buyukluk Hesabi: tepe ucta istek/s
const DOYUM = 400, GUVENLI = 200;    // K02 Yuk Dengeleyici Rolu: varsayim Y1 ve Y1 x Y2
const SERI_PAY = 0.1980;             // K02 Oturum Yapiskanligi: en buyuk oturumun payi
const DEPO_ISTEK = 138.89;           // K01: depoya ulasan istek/s
const YAZMA_GIRIS = 0.17;            // K01: yazma giris Mbit/s
const DUZEN = [["yapiskan (K02)", 3, 1.173], ["yapiskan (K02)", 4, 1.505],
  ["durumsuz", 3, 1.000], ["durumsuz", 4, 1.000]];

console.log(`${"duzen".padEnd(16)}${"kopya".padStart(6)}${"dengesizlik".padStart(12)}` +
  `${"en yuklu istek/s".padStart(18)}${"doluluk".padStart(9)}${"gereken kopya".padStart(15)}`);
for (const [ad, n, d] of DUZEN) {
  const enYuklu = (d * TEPE_UC) / n;
  console.log(`${ad.padEnd(16)}${String(n).padStart(6)}${d.toFixed(3).padStart(12)}` +
    `${enYuklu.toFixed(2).padStart(18)}${(enYuklu / DOYUM).toFixed(3).padStart(9)}` +
    `${String(Math.ceil((d * TEPE_UC) / GUVENLI)).padStart(15)}`);
}
console.log(`\nyapiskan duzende olcekleme ust siniri = 1/${SERI_PAY} = ${(1 / SERI_PAY).toFixed(2)} kat;` +
  ` durumsuz duzende boyle bir ust sinir yok`);

// U1 maliyeti: her takip istegi oturum listesinden bir okuma ve bir yazma yapiyor (duzenekteki davranis)
const islem = 2 * TEPE_UC;
console.log(`\nU1 -> durum deposu islem/s = ${islem.toFixed(2)}, K01'in depoya ulasan ` +
  `${DEPO_ISTEK} istek/s'sinin ${(islem / DEPO_ISTEK).toFixed(2)} kati`);
console.log(`U1 yalniz okuma olsaydi = ${TEPE_UC.toFixed(2)} islem/s (${(TEPE_UC / DEPO_ISTEK).toFixed(2)} kat)`);

// Ikinci yol: durum istekte tasinir; govde buyur
const liste = (k) => JSON.stringify(Array.from({ length: k }, (_, i) => `TR-${4820 + i}`));
console.log(`\n${"listedeki numara".padEnd(18)}${"oturum govdesi bayt".padStart(21)}` +
  `${"ek giris Mbit/s".padStart(17)}${"K01 yazma girisine oran".padStart(25)}`);
for (const k of [8, 40]) {
  const bayt = Buffer.byteLength(liste(k));
  const mbit = (TEPE_UC * bayt * 8) / 1e6;
  console.log(`${String(k).padEnd(18)}${String(bayt).padStart(21)}${mbit.toFixed(3).padStart(17)}` +
    `${(mbit / YAZMA_GIRIS).toFixed(2).padStart(25)}`);
}
duzen            kopya dengesizlik  en yuklu istek/s  doluluk  gereken kopya
yapiskan (K02)       3       1.173            200.93    0.502              4
yapiskan (K02)       4       1.505            193.35    0.483              4
durumsuz             3       1.000            171.30    0.428              3
durumsuz             4       1.000            128.47    0.321              3

yapiskan duzende olcekleme ust siniri = 1/0.198 = 5.05 kat; durumsuz duzende boyle bir ust sinir yok

U1 -> durum deposu islem/s = 1027.78, K01'in depoya ulasan 138.89 istek/s'sinin 7.40 kati
U1 yalniz okuma olsaydi = 513.89 islem/s (3.70 kat)

listedeki numara    oturum govdesi bayt  ek giris Mbit/s  K01 yazma girisine oran
8                                    81            0.333                     1.96
40                                  401            1.649                     9.70

Üstteki tablonun sayıları hesap sınıfındadır. Durumsuz düzende dengesizlik 1,000 olduğu için uçtaki 513,89 istek/s üç kopyaya eşit bölünüyor: kopya başına 171,30 istek/s ve 0,428 doluluk. Gereken kopya sayısı 4’ten 3’e iniyor — yapışkan düzenin faturası bir kopyaydı ve geri alındı. Alttaki iki satır daha önemlisini söylüyor: durumsuz düzende kopya eklemek işe yarıyor. Dördüncü kopya en yüklü kopyayı 171,30’dan 128,47 istek/s’ye indiriyor, oysa yapışkan düzende aynı ekleme 200,93’ten 193,35’e indirmişti. Ölçekleme üst sınırı da kalkıyor: seri pay 0,1980 iken üst sınır 5,05 katıydı, durumsuz düzende böyle bir sınır yok.

Bedel alt bölümlerdedir ve bu kursun varsayımıyla yazılır; K01’in tablosuna eklenmez.

U1 — her takip isteği oturum durumundan bir okuma ve bir yazma yapar. Gerekçe: düzenekteki davranış budur, izleme listesi her sorguda okunup güncelleniyor. Duyarlılık aşağıda hesaplandı.

U1 ile durum deposuna saniyede 1027,78 işlem düşüyor. Bu, K01’in gönderi deposuna ulaşan 138,89 istek/s’sinin 7,40 katıdır. Sayının büyüklüğü bir uyarıdır: durumu süreç dışına taşımak onu yok etmez, başka bir yere taşır ve o yer artık sistemin en çok istek alan bileşenidir. Duyarlılık tek yönlüdür — oturum yalnız okunsaydı 513,89 işlem/s, yani 3,70 kat olurdu; yazma isteğe bağlı olduğu için bu, tasarımla küçültülebilen bir sayıdır.

İkinci yol durumu hiç saklamamaktır: liste isteğin kendisinde taşınır. Alttaki tablo bunun faturasını veriyor. Sekiz numaralı bir liste 81 bayttır ve 513,89 istek/s’de 0,333 Mbit/s ek giriş demektir — K01’in yazma girişinin 1,96 katı. Liste kırk numaraya çıktığında 401 bayt ve 1,649 Mbit/s, yani 9,70 kat. İki yolun ayrımı buradan okunur: durum küçük ve sınırlıysa istekte taşınabilir, büyüyebiliyorsa taşınamaz.

Özet

  • Durumsuz servis, iki isteği birbirine bağlayan hiçbir veriyi kendi süreç belleğinde tutmayan servistir; ayrımı yapan soru, veri kaybolduğunda yanıtın değişip değişmediğidir.
  • bellek kipinde sekiz isteğin dördü oturumu buldu ve liste iki kopya arasında bölündü (k1=4, k2 görmüyor); kopya yeniden başlatıldığında liste tümüyle kayboldu.
  • disari kipinde sekiz isteğin sekizi oturumu buldu, iki kopya da aynı sekiz numarayı saydı ve yeniden başlatılan kopya listeyi eksiksiz okudu.
  • Durumsuz düzende dengesizlik 1,000: gereken kopya 4’ten 3’e, kopya başına yük 200,93’ten 171,30 istek/s’ye iniyor ve dördüncü kopya artık işe yarıyor (128,47 istek/s).
  • Ölçekleme üst sınırı kalkıyor: yapışkan düzende seri pay 0,1980 üst sınırı 5,05 katta tutuyordu.
  • Bedel taşınıyor, yok olmuyor: U1 ile durum deposuna 1027,78 işlem/s (K01’in depo yükünün 7,40 katı), ya da durum istekte taşınırsa 81 bayt için 0,333 Mbit/s ek giriş, kırk numaralı listede 1,649 Mbit/s.

Sonraki Adım

Kopyalar artık birbirinin eşi ve yük serbestçe bölünebiliyor. Ama ölçümün bir parçası el yazısıyla duruyor: sürücü betik kopyaların bağlantı noktalarını komut satırından aldı, istemci de aynı sayıları elle taşıdı. Trafik Katmanı kursunun dengeleyicisi de kopya listesini aynı biçimde almıştı. Kopya sayısı değiştiğinde, bir kopya düştüğünde ya da yeni bir kopya ayağa kalktığında bu listeyi kim güncelliyor. Sonraki ders o soruyu ele alır: adres bilgisinin nerede durduğunu, bir kopyanın listeye girip çıkmasının ne kadar sürdüğünü ve o pencerede kaç isteğin yanlış adrese gittiğini ölçer — ve bu işin yük dengeleyicinin sağlık denetiminden neden ayrı bir karar olduğunu gösterir.

İ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