İçeriğe geç
academia.sh

Ders 08 / 18

Eşzamanlı Servis Çağrıları

Sınırın karşısına geçen çağrının gerçek süreçlerle ölçülmesi: zincir uzunluğuna göre uçtan uca turun büyümesi, dağıtılan çağrının aynı işi daha az turda bitirmesi, bir dış isteğin kaç iç isteğe dönüştüğünü veren çağrı çarpanı ve bir sürecin kapanmasında yanıtsız kalan giriş uçlarının payı.

İçindekiler

Önceki ders sınırı bir ölçüte bağladı ve tabloda sessizce duran bir sayı bıraktı: ağa dönüşen ithal. Aynı süreçteki bir ithal kenarı işlev çağrısıdır; iki dağıtım birimi arasındaki aynı kenar ağ çağrısıdır. Bu ders o dönüşümü modelle değil, gerçekten ayrı işletim sistemi süreçleriyle kurar ve farkı tur cinsinden sayar.

Zaman aşımı, yeniden deneme ve uçtan uca bütçenin zincire bölüştürülmesi Uygulama Katmanı ve Servis Etkileşimi kursunda kuruldu ve ölçüldü; o aritmetik burada tekrarlanmaz. Buradaki üç ölçü şudur: zincir uzunluğuna göre uçtan uca tur, bir dış isteğin doğurduğu iç istek sayısı ve bir süreç kapandığında yanıtsız kalan isteklerin payı. Eşzamanlı çağrı, çağıranın yanıtı bekleyip sonucu kendi yanıtına kattığı çağrıdır.

Aynı İş, İki Yerleşim

Ödünç verme iş akışı dört adımdan oluşur: ödünç kaydı, üyelik denetimi, ödenmemiş ücret denetimi ve nüsha ayırma. Aşağıdaki servis tek bir kaynak dosyadır; hangi rolü üstleneceği komut satırı değişkenlerinden gelir. Her adım aynı süreyi harcar, böylece ölçülen süre iş adımı sayısına bölünebilir.

// servis.mjs — <ad> <port> <kip: paralel|sirali> <adim> [ardil-port...]; uzun omurlu surec
import { createServer } from "node:http";
const [ad, port, kip, adim, ...ardil] = process.argv.slice(2);
if (!port) { console.log("kullanim: node servis.mjs <ad> <port> <kip> <adim> [ardil...]"); process.exit(1); }
const IS = 25;                        // bir is adiminin suresi (ms); olcum bu birime bolunur
let islenen = 0;
const cagir = async (p) => {          // ardilin durum kodu denetlenir: hata sinirin karsisina gecer
  const y = await fetch(`http://127.0.0.1:${p}/is`);
  if (y.status !== 200) throw new Error(`ardil ${p} durum ${y.status}`);
  return y.json();
};

createServer(async (istek, yanit) => {
  if (istek.url === "/sayac") { yanit.end(JSON.stringify({ ad, islenen })); return; }
  if (istek.url === "/sifirla") { islenen = 0; yanit.end("sifir"); return; }
  islenen += 1;
  for (let i = 0; i < Number(adim); i += 1) await new Promise((c) => setTimeout(c, IS));
  try {
    let alt = [];
    if (kip === "sirali") for (const p of ardil) alt.push(await cagir(p));
    else alt = await Promise.all(ardil.map(cagir));
    yanit.end(JSON.stringify({ ad, adim: Number(adim), alt }));
  } catch (e) {
    yanit.statusCode = 502;
    yanit.end(JSON.stringify({ ad, hata: e.message }));
  }
}).listen(Number(port));

Aynı dört adım iki yerleşimde koşacak. Bölünmüş yerleşimde dört süreç vardır: ödünç servisi üyelik ile katalog servislerini çağırır, üyelik servisi ücretlendirme servisini çağırır. Tek parça yerleşiminde dört adımın dördü de tek süreçte ardışık koşar; ödünç ucundaki adim değeri dörttür ve hiç ardıl çağrısı yoktur. Üçüncü bir yerleşim karşılaştırma içindir: ödünç servisi iki ardılını paralel yerine sırayla çağırır.

Ardıl çağrısında durum kodunun denetlendiği satır dersin geri kalanını belirler. Süreç içinde bir adımın başarısızlığı özel durum olarak yukarı yayılır; sınırın karşısında ise yalnız bir durum kodudur. Çağıran o kodu denetlemezse hata yayılmaz, yutulur: yanıt başarılı görünür ama içindeki üyelik denetimi hiç yapılmamıştır.

Ölçüm

Ölçüm aracı üç kipte çalışır. Gecikme kipi her giriş ucuna üç istek gönderir ve ortancayı iş adımı süresine bölerek turu bulur; süre ölçümü ortama bağlıdır, tur sayısı değildir. Çarpan kipi sayaçları sıfırlar, tek uca beş istek gönderir ve süreçlerin işlediği istekleri toplar. Arıza kipi her giriş ucuna birer istek atıp yanıtsız kalanları sayar.

SS2 — dış istekler dört giriş ucuna eşit dağılır. Gerekçe: etkilenen istek payı, kapanan servise bağımlı giriş uçlarının trafikteki ağırlığıdır; ağırlıklar bilinmediğinde eşit dağılım payı ölçülebilir kılar. Gerçek dağılım bilindiğinde aynı hesap ağırlıklarla yapılır.

// olc.mjs — <kip: gecikme|carpan|ariza> [ad]; servis.mjs surecleri ayaktayken kosar
const IS = 25;                                   // servis.mjs ile ayni is adimi suresi
const UC = { ucretlendirme: 8941, uyelik: 8942, katalog: 8943, odunc: 8944,
  "sirali-odunc": 8945, "tek-surec": 8946 };
const ARIZA_GIRIS = ["ucretlendirme", "uyelik", "katalog", "odunc"];
const iste = async (p) => {
  const y = await fetch(`http://127.0.0.1:${p}/is`);
  if (y.status !== 200) throw new Error(`durum ${y.status}`);
  return y.json();
};
const dugum = (a) => 1 + (a.alt ?? []).reduce((s, x) => s + dugum(x), 0);
const adimlar = (a) => a.adim + (a.alt ?? []).reduce((s, x) => s + adimlar(x), 0);
const [kip, hedef] = process.argv.slice(2);

if (kip === "gecikme") {
  console.log(`${"giris".padEnd(14)}${"surec".padStart(7)}${"ag atlamasi".padStart(13)}` +
    `${"is adimi".padStart(10)}${"olculen tur".padStart(13)}`);
  let enBuyukEk = 0;
  for (const ad of Object.keys(UC)) {
    const sure = [];
    let agac;
    for (let i = 0; i < 3; i += 1) {
      const t = performance.now();
      agac = await iste(UC[ad]);
      sure.push(performance.now() - t);
    }
    const orta = sure.sort((a, b) => a - b)[1];
    const d = dugum(agac), tur = Math.floor(orta / IS);
    enBuyukEk = Math.max(enBuyukEk, orta - tur * IS);
    console.log(`${ad.padEnd(14)}${String(d).padStart(7)}${String(d - 1).padStart(13)}` +
      `${String(adimlar(agac)).padStart(10)}${String(tur).padStart(13)}`);
  }
  console.log(`olculen sure = tur x ${IS} ms + ek yuk; ek yuk bu kosumda ` +
    `${Math.ceil(enBuyukEk / IS) * IS} ms altinda (bir is adimindan az)`);
} else if (kip === "carpan") {
  for (const ad of Object.keys(UC)) await fetch(`http://127.0.0.1:${UC[ad]}/sifirla`);
  const N = 5;
  for (let i = 0; i < N; i += 1) await iste(UC.odunc);
  let toplam = 0;
  for (const ad of Object.keys(UC)) {
    const { islenen } = await (await fetch(`http://127.0.0.1:${UC[ad]}/sayac`)).json();
    toplam += islenen;
    if (islenen > 0) console.log(`  ${ad.padEnd(14)}${String(islenen).padStart(8)} istek`);
  }
  console.log(`odunc ucuna ${N} dis istek -> toplam ${toplam} islenen istek, ` +
    `cagri carpani x${toplam / N} (dis istek basina ${toplam / N - 1} ic istek)`);
} else if (kip === "ariza") {
  const dusen = [];
  for (const ad of ARIZA_GIRIS) {
    try { await iste(UC[ad]); } catch { dusen.push(ad); }
  }
  console.log(`${hedef.padEnd(16)}${(dusen.join(",") || "-").padEnd(36)}` +
    `${dusen.length}/${ARIZA_GIRIS.length}`);
}

Aşağıdaki kabuk bloğu altı süreci ayağa kaldırır, üç ölçümü koşturur, sonra sırayla üç servisi kapatıp her defasında arıza kipini çalıştırır ve kapattığı servisi geri başlatır.

baslat() {
  case $1 in
    ucretlendirme) node servis.mjs ucretlendirme 8941 paralel 1 & ;;
    uyelik)        node servis.mjs uyelik 8942 paralel 1 8941 & ;;
    katalog)       node servis.mjs katalog 8943 paralel 1 & ;;
    odunc)         node servis.mjs odunc 8944 paralel 1 8942 8943 & ;;
  esac
}
for s in ucretlendirme uyelik katalog odunc; do baslat $s; done
node servis.mjs sirali-odunc 8945 sirali 1 8942 8943 &
node servis.mjs tek-surec 8946 paralel 4 &
sleep 1

node olc.mjs gecikme
echo
echo "cagri carpani:"
node olc.mjs carpan
echo
printf '%-16s%-36s%s\n' "kapali servis" "yanitsiz kalan giris" "etkilenen istek payi"
for s in ucretlendirme katalog odunc; do
  pkill -f "servis.mjs $s" ; sleep 0.3
  node olc.mjs ariza $s
  baslat $s ; sleep 0.5
done
pkill -f "node servis.mjs"
giris           surec  ag atlamasi  is adimi  olculen tur
ucretlendirme       1            0         1            1
uyelik              2            1         2            2
katalog             1            0         1            1
odunc               4            3         4            3
sirali-odunc        4            3         4            4
tek-surec           1            0         4            4
olculen sure = tur x 25 ms + ek yuk; ek yuk bu kosumda 25 ms altinda (bir is adimindan az)

cagri carpani:
  ucretlendirme        5 istek
  uyelik               5 istek
  katalog              5 istek
  odunc                5 istek
odunc ucuna 5 dis istek -> toplam 20 islenen istek, cagri carpani x4 (dis istek basina 3 ic istek)

kapali servis   yanitsiz kalan giris                etkilenen istek payi
ucretlendirme   ucretlendirme,uyelik,odunc          3/4
katalog         katalog,odunc                       2/4
odunc           odunc                               1/4

Zincir Uzunluğu Turu Belirliyor

İlk üç satır zinciri uzatarak okunuyor. Ücretlendirme ucuna gelen istek bir süreçte biter: sıfır atlama, bir tur. Üyelik ucu ücretlendirmeyi çağırır: iki süreç, bir atlama, iki tur. Ödünç ucu dört süreci birden işletir: üç atlama, dört iş adımı. Zincir gecikmesi burada tur cinsinden görünür hâle geliyor: uçtan uca tur, zincirin ardışık adım sayısıdır ve ölçüm bunu geri veriyor.

Dördüncü ve altıncı satır dersin asıl karşılaştırmasıdır. Ödünç ucu dört iş adımını üç turda bitiriyor, tek süreç aynı dört adımı dört turda. Kazanç bölünmeden gelmiyor, bölünmenin açtığı fırsattan geliyor: üyelik dalı ile katalog dalı birbirini beklemek zorunda olmadığı için paralel çağrılıyor ve uçtan uca süre iki dalın toplamı değil büyüğü oluyor. Beşinci satır bunu doğruluyor — aynı dört süreç, aynı üç atlama, ama ardılları sırayla çağıran ödünç servisi dört turda bitiriyor. Dağıtılan çağrı bir turu geri kazandırır; sıralı çağrı kazanmaz.

Süre ölçümü ortama bağlıdır ve loopback üzerinde bir atlamanın bedeli küçüktür; ek yükün tamamı bir iş adımının altında kaldı. Gerçek bir ağda aynı üç atlama bu kadar ucuz değildir, ama tur sayısı değişmez: tur, koşumdan bağımsız niceliktir ve gecikme bütçesi turlarla çarpılır.

Çarpan ve Etkilenen Pay

Çağrı çarpanı ikinci ölçüdür. Ödünç ucuna gelen beş dış istek, süreçlerde toplam yirmi istek işletiyor: çarpan dört, yani her dış istek başına üç iç istek. Bu sayı doğrudan sınır kararının sonucudur; önceki dersin ölçtüğü sınırı geçen ithal kenarları, koşum sırasında iç isteğe dönüşen kenarlardır. Sınırı geçen kenar sayısı arttıkça çarpan artar ve her servis kendi kapasitesini kendi trafiğine göre değil, çarpanla büyütülmüş trafiğe göre seçmek zorunda kalır.

Son tablo yeni arıza kipini sayıya çeviriyor. Ücretlendirme süreci kapandığında yalnız kendi ucu değil, ona bağımlı olan üyelik ve ödünç uçları da yanıtsız kalıyor: dört giriş ucunun üçü, SS2 altında isteklerin yüzde yetmiş beşi. Katalog kapandığında iki uç, ödünç kapandığında bir uç etkileniyor. Etkilenen pay, kapanan servisin bağımlılık kapanışıdır: zincirin dibindeki servis en pahalıdır, çünkü üstündeki her uç ona bağlıdır. Tek parça yerleşiminde bu tablo tek satırdır — süreç kapanırsa isteklerin tamamı düşer, ama kısmi arıza diye bir durum da yoktur.

Üçlü şöyle toparlanır. Ucuzlayan: dört iş adımı üç turda bitiyor ve her servis kendi başına ölçeklenebiliyor; bir servisin kapanması diğer üç ucu ayakta bırakıyor. Pahalılaşan: istek başına üç ağ atlaması, dört süreç, dört kat işlenen istek ve dörde bölünmüş bir başlatma düzeni. Doğan arıza kipi: kısmi arıza. Sistem artık ya tamamen ayakta ya tamamen kapalı değil; giriş ucuna göre değişen bir yanıt verme oranı var ve ardıl hatasının durum kodu denetlenmezse bu oran hiç görünmeden, eksik iş yapılmış başarılı yanıtlara dönüşüyor.

Özet

  • Uçtan uca tur zincirin ardışık adım sayısıdır: ölçüm sıfır, bir ve üç atlamalı uçlarda sırasıyla 1, 2 ve 3 tur verdi; ek yük bir iş adımının altında kaldı.
  • Aynı dört iş adımı tek süreçte 4 turda, dört süreçte 3 turda bitiyor; kazanç bölünmenin değil, bölünmenin açtığı paralel çağrının sonucudur — ardılları sırayla çağıran yerleşim 4 turda bitiyor.
  • Çağrı çarpanı sınırı geçen kenarların koşum karşılığıdır: 5 dış istek 20 işlenen isteğe dönüştü, dış istek başına 3 iç istek.
  • Bir sürecin kapanmasında etkilenen istek payı, o servisin bağımlılık kapanışıdır: ücretlendirme için 3/4, katalog için 2/4, ödünç için 1/4.
  • Sınır, hatayı özel durumdan durum koduna çevirir; çağıran durum kodunu denetlemezse arıza yayılmaz, yutulur ve başarılı görünen yanıt eksik iş içerir.

Sonraki Adım

Bu ölçümlerin hepsi tek bir varsayımın içindeydi: çağıran, ardılın işi bitene kadar bekliyor. Çarpan da, tur da, etkilenen pay da o beklemeden doğuyor — ödünç servisi bildirimin gönderilmesini beklediği için bildirim servisinin kapanması ödünç isteğini düşürüyor. Oysa iş akışındaki her adım yanıtın verilebilmesi için tamamlanmak zorunda değil. Sonraki ders aynı iş akışını ikinci kez kurar: yanıt yolundaki adımlardan biri çağrı olmaktan çıkıp bir olaya dönüşür ve tüketicisi kendi zamanında işler. Ölçülecek olanlar da bellidir — yanıt yolundan düşen iş adımı, yayıncı ile tüketici ayrı dağıtım birimleri olduğunda ortaya çıkan sözleşme yükü ve olayın yazılmasıyla etkisinin görünmesi arasındaki tutarlılık penceresi.

İ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