Ders 04 / 18
Mikroservisler
Aynı ödünç iş akışının gerçekten ayrı süreçlere bölünmesi: beş süreç, sekiz uç, istek başına dokuz ağ atlaması, zincir gecikmesi; tek bir dağıtım biriminin diğerleri durmadan yeniden yayına çıkması ve bir sürecin düşmesiyle doğan yarım kalmış iş akışı.
İçindekiler
Önceki derste birimler ayrıldı ama aynı süreçte kaldı: gonder bir işlev çağrısıydı, ileti
bir nesneydi. Bu ders aynı ödünç iş akışını gerçekten ayrı süreçlere böler. Her birim
kendi bağlantı noktasında dinler, kendi verisini tutar, kendi başına başlatılır ve
durdurulur. Her birimin kendi dağıtım birimi olduğu bu düzene mikroservis (microservice)
denir; biçemin tanımı Mimari Biçemler kursunda yapıldı, burada ölçülen şey bölmenin koşumda
ve kodda görünen bedelidir.
MS5. Beş süreç aynı makinede, geri döngü arayüzü üzerinden konuşur. Gerçek bir ağın gecikmesi, paket kaybı ve bölünmesi bu düzenekte yoktur. Modelin taşıdığı özellikler şunlardır: çağrı süreç sınırını geçer, serileştirilir, hedef süreç bağımsız olarak durabilir ve bağımsız olarak yeniden başlatılabilir.
Beş Süreç
Her servis bağlantı noktasını komut satırından alır; komşularının adresini ortam
değişkeninden okur. Durum değiştiren uçlar POST ile çağrılır.
// katalog-servisi.mjs — kitap birimi; kendi surecinde, kendi verisiyle import { createServer } from "node:http"; const port = Number(process.argv[2]); if (!port) { console.log("kullanim: node katalog-servisi.mjs <port>"); process.exit(0); } const kitap = { 1: { durum: "rafta" }, 2: { durum: "rafta" }, 3: { durum: "rafta" } }; createServer((istek, yanit) => { const u = new URL(istek.url, "http://y"); const no = u.searchParams.get("kitapNo"); if (u.pathname === "/durum") return yanit.end(JSON.stringify({ durum: kitap[no].durum })); if (u.pathname === "/odunc-isaretle") { kitap[no].durum = "odunc"; return yanit.end("{}"); } yanit.statusCode = 404; yanit.end("{}"); }).listen(port, () => console.log(`katalog ${port}`));
// uyelik-servisi.mjs — uye birimi; kendi surecinde, kendi verisiyle import { createServer } from "node:http"; const port = Number(process.argv[2]); if (!port) { console.log("kullanim: node uyelik-servisi.mjs <port>"); process.exit(0); } const uye = { 4: { ad: "Emre", acikOdunc: 1, sinir: 5, borc: 0 }, 5: { ad: "Selin", acikOdunc: 3, sinir: 3, borc: 0 } }; createServer((istek, yanit) => { const u = new URL(istek.url, "http://y"); const k = uye[u.searchParams.get("uyeNo")]; if (k && u.pathname === "/getir") return yanit.end(JSON.stringify(k)); if (k && u.pathname === "/odunc-ekle") { k.acikOdunc += 1; return yanit.end("{}"); } if (k && u.pathname === "/borc-ekle") { k.borc += Number(u.searchParams.get("tutar")); return yanit.end("{}"); } yanit.statusCode = 404; yanit.end("{}"); }).listen(port, () => console.log(`uyelik ${port}`));
// ucret-servisi.mjs — gecikme ucreti birimi; gunluk oran burada import { createServer } from "node:http"; const port = Number(process.argv[2]); if (!port) { console.log("kullanim: node ucret-servisi.mjs <port>"); process.exit(0); } const GUNLUK = 2; createServer((istek, yanit) => { const u = new URL(istek.url, "http://y"); if (u.pathname !== "/hesapla") { yanit.statusCode = 404; return yanit.end("{}"); } const gun = Number(u.searchParams.get("gun")); yanit.end(JSON.stringify({ ucret: gun > 0 ? gun * GUNLUK : 0 })); }).listen(port, () => console.log(`ucret ${port}`));
// bildirim-servisi.mjs — bildirim birimi; uye adini uyelik surecinden ister import { createServer } from "node:http"; const port = Number(process.argv[2]); if (!port) { console.log("kullanim: node bildirim-servisi.mjs <port>"); process.exit(0); } createServer(async (istek, yanit) => { const u = new URL(istek.url, "http://y"); if (u.pathname !== "/gonder") { yanit.statusCode = 404; return yanit.end("{}"); } const y = await fetch(`http://127.0.0.1:${process.env.UYELIK}/getir?uyeNo=${u.searchParams.get("uyeNo")}`); console.log(`bildirim -> ${(await y.json()).ad}: ${u.searchParams.get("metin")}`); yanit.end("{}"); }).listen(port, () => console.log(`bildirim ${port}`));
Ödünç servisi, üçüncü dersteki odunc.mjs ile aynı sırayı yürütür. Tek fark, her adımın
artık bir ağ çağrısı olmasıdır. Yaptığı atlamaları sayar ve zincirin süresini kendi
günlüğüne yazar.
// odunc-servisi.mjs — is akisini yuruten surec; digerlerini ag uzerinden cagirir import { createServer } from "node:http"; const port = Number(process.argv[2]); if (!port) { console.log("kullanim: node odunc-servisi.mjs <port>"); process.exit(0); } const A = { katalog: process.env.KATALOG, uyelik: process.env.UYELIK, ucret: process.env.UCRET, bildirim: process.env.BILDIRIM }; let atlama = 0; const cagir = async (hedef, yol, yontem = "GET") => { atlama += 1; return (await fetch(`http://127.0.0.1:${A[hedef]}${yol}`, { method: yontem })).json(); }; createServer(async (istek, yanit) => { const u = new URL(istek.url, "http://y"); if (u.pathname !== "/odunc") { yanit.statusCode = 404; return yanit.end("{}"); } const uyeNo = u.searchParams.get("uyeNo"), kitapNo = u.searchParams.get("kitapNo"); const gun = u.searchParams.get("gun") ?? 0; atlama = 0; const t0 = performance.now(); try { const uye = await cagir("uyelik", `/getir?uyeNo=${uyeNo}`); if (uye.acikOdunc >= uye.sinir) throw new Error("odunc siniri asildi"); if ((await cagir("katalog", `/durum?kitapNo=${kitapNo}`)).durum !== "rafta") throw new Error("kitap rafta degil"); await cagir("katalog", `/odunc-isaretle?kitapNo=${kitapNo}`, "POST"); await cagir("uyelik", `/odunc-ekle?uyeNo=${uyeNo}`, "POST"); const { ucret } = await cagir("ucret", `/hesapla?gun=${gun}`); if (ucret > 0) await cagir("uyelik", `/borc-ekle?uyeNo=${uyeNo}&tutar=${ucret}`, "POST"); await cagir("bildirim", `/gonder?uyeNo=${uyeNo}&metin=kitap+${kitapNo}`, "POST"); console.log(`odunc ${kitapNo}/${uyeNo}: atlama ${atlama}, sure ${Math.round(performance.now() - t0)} ms`); yanit.end(JSON.stringify({ durum: "verildi", ucret, atlama })); } catch (h) { console.log(`odunc ${kitapNo}/${uyeNo}: HATA ${h.message}, atlama ${atlama}`); yanit.statusCode = 400; yanit.end(JSON.stringify({ durum: "basarisiz", hata: h.message, atlama })); } }).listen(port, () => console.log(`odunc ${port}`));
Ayağa Kaldırma
# baslat.sh — bes surec ayaga kalkar; her surecin ciktisi kendi gunlugune yazilir rm -f pid.txt node katalog-servisi.mjs 8792 >katalog.log 2>&1 & echo "katalog $!" >>pid.txt node uyelik-servisi.mjs 8793 >uyelik.log 2>&1 & echo "uyelik $!" >>pid.txt node ucret-servisi.mjs 8794 >ucret.log 2>&1 & echo "ucret $!" >>pid.txt UYELIK=8793 node bildirim-servisi.mjs 8795 >bildirim.log 2>&1 & echo "bildirim $!" >>pid.txt KATALOG=8792 UYELIK=8793 UCRET=8794 BILDIRIM=8795 \ node odunc-servisi.mjs 8791 >odunc.log 2>&1 & echo "odunc $!" >>pid.txt for p in 8791 8792 8793 8794 8795; do for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:$p/" && break; sleep 0.2; done done echo "ayaga kalkan surec: $(wc -l <pid.txt | tr -d ' ') baslatma adimi: 5 arka plan + 1 hazirlik beklemesi" echo "uc: $(grep -oh '"/[a-z-]*"' ./*.mjs | sort -u | wc -l | tr -d ' ') dagitim birimi: 5" grep -oh '"/[a-z-]*"' ./*.mjs | sort -u | tr -d '"' | tr '\n' ' ' echo
ayaga kalkan surec: 5 baslatma adimi: 5 arka plan + 1 hazirlik beklemesi uc: 8 dagitim birimi: 5 /borc-ekle /durum /getir /gonder /hesapla /odunc /odunc-ekle /odunc-isaretle
Birinci derste bu sayı birdi: node uygulama.mjs. Burada beş arka plan süreci ve bir
hazırlık beklemesi var; bağlantı noktaları ve komşu adresleri dışarıdan verilmek zorunda.
İstek Başına Atlama
curl -s "http://127.0.0.1:8791/odunc?kitapNo=1&uyeNo=4&gun=3" echo curl -s "http://127.0.0.1:8791/odunc?kitapNo=2&uyeNo=5" echo
{"durum":"verildi","ucret":6,"atlama":7}
{"durum":"basarisiz","hata":"odunc siniri asildi","atlama":1}
Ödünç servisi 7 çağrı yaptı. Bildirim servisi üyenin adını almak için bir çağrı daha yaptı; istemcinin ödünç servisine gönderdiği istekle birlikte istek başına toplam 9 ağ atlaması oluyor. Üçüncü derste aynı iş akışı 8 ileti üretmişti — ileti sayısı değişmedi, her ileti süreç sınırını geçer oldu.
Zincir sıralı ilerliyor: her çağrı bir öncekinin dönüşünü bekliyor, yani gecikmeler
toplanıyor. Ödünç servisi odunc.log dosyasına her istek için bir satır yazar; birinci
isteğin satırı bu koşumda odunc 1/4: atlama 7, sure 21 ms oldu.
Süre alanı makineye, çekirdeğe ve o andaki
yüke bağlıdır — yukarıdaki değer bu koşumda okunmuştur ve her koşumda değişir. Koşumdan
bağımsız nicelik 7 sıralı turdur ve bu sayı ancak zincir kısaltılarak düşer. İkinci
istek üyelik denetiminde durduğu için tek atlamayla bitti: hata erken çıktığında zincirin
geri kalanı hiç kurulmuyor.
Bağımsız Dağıtım
Asıl kazanç şurada ölçülür: ücret oranı değişsin.
# yeniden.sh — yalniz ucret sureci degisir ve yeniden baslatilir; digerleri hic durmaz kill "$(awk '$1 == "ucret" { print $2 }' pid.txt)" sed -i.y 's/const GUNLUK = 2;/const GUNLUK = 5;/' ucret-servisi.mjs rm -f ucret-servisi.mjs.y node ucret-servisi.mjs 8794 >>ucret.log 2>&1 & echo "ucret $!" >>pid.txt for i in $(seq 40); do curl -s -o /dev/null "http://127.0.0.1:8794/" && break; sleep 0.2; done echo "yeniden baslatilan surec: 1 / 5 dokunulan dagitim birimi: 1" curl -s "http://127.0.0.1:8791/odunc?kitapNo=2&uyeNo=4&gun=3" echo
yeniden baslatilan surec: 1 / 5 dokunulan dagitim birimi: 1
{"durum":"verildi","ucret":15,"atlama":7}
Yeni oran yürürlükte. Katalog, üyelik, bildirim ve ödünç süreçleri hiç durmadı; bellekteki durumlarını korudular. Birinci derste aynı tek satırlık değişiklik yedi modülü birlikte yeniden yayına çıkarıyordu; burada bir dağıtım birimi yeniden başladı.
Bu kazanç sınırsız değil. Kanonik acikOdunc alanı iki dağıtım biriminde geçiyor — üyelik
ve ödünç. Onun yeniden adlandırılması, üçüncü derste tek birim içinde 3 dosyaya dokunan bir
değişikken, burada iki ayrı dağıtım biriminin uyumlu bir sırayla yayına çıkmasını
gerektirir. Bağımsız dağıtım, sözleşmenin değişmediği değişiklikler için bağımsızdır.
Kısmi Hata
# arizali.sh — bildirim sureci durur; ayni istek yolun sonunda kirilir kill "$(awk '$1 == "bildirim" { print $2 }' pid.txt)" sleep 0.5 curl -s "http://127.0.0.1:8791/odunc?kitapNo=3&uyeNo=4" echo curl -s "http://127.0.0.1:8792/durum?kitapNo=3" echo curl -s "http://127.0.0.1:8793/getir?uyeNo=4" echo
{"durum":"basarisiz","hata":"fetch failed","atlama":6}
{"durum":"odunc"}
{"ad":"Emre","acikOdunc":4,"sinir":5,"borc":21}
İstek başarısız döndü, ama kitap ödünçte ve üyenin açık ödünç sayısı arttı. Altıncı atlamada kırılan zincir, ilk beş atlamanın etkisini geri almadı. Tek parçada bu durum yoktu: bir istisna bütün gövdeyi terk ediyor, hiçbir modül yarım bir işi kesinleştirmiş olmuyordu.
Aynı çıktının ikinci satırı başka bir şey söylüyor: katalog servisi hâlâ cevap veriyor. Bildirim süreci düştüğünde sistemin tamamı durmadı — bildirime dokunmayan yollar çalışmaya devam eder. Arıza artık kısmidir; bu hem kazanç hem de yeni bir sorun, çünkü sistemin durumu “ayakta” ve “düşük” arasında bir yerdedir.
# durdur.sh — butun surecleri durdurur while read -r ad p; do kill "$p" 2>/dev/null; done <pid.txt echo "durduruldu"
durduruldu
Üç Sütun
| Mikroservisler | |
|---|---|
| Ucuzlattığı | Bir oran değişikliği 5 dağıtım biriminin 1’ini yeniden başlattı; her birim kendi verisine sahip, ölçekleme birimi tek servis |
| Pahalılaştırdığı | İstek başına 9 ağ atlaması ve 7 sıralı tur; başlatma 1 adımdan 6 adıma çıktı; adresler dışarıdan verilmek zorunda |
| Doğan arıza kipi | Zincirin ortasında kırılan istek yarım kalmış durum bırakıyor; arıza kısmi, sistem ne tam ayakta ne tam düşük |
Özet
- Aynı ödünç iş akışı 5 ayrı sürece bölündü: 8 uç, 5 dağıtım birimi, istek başına 9 ağ atlaması, 7 sıralı tur.
- İleti sayısı üçüncü derse göre değişmedi; değişen şey her iletinin süreç sınırını geçmesi ve sıralı turların gecikmelerinin toplanmasıdır.
- Ayağa kaldırma 1 adımdan 6 adıma çıktı; her sürecin bağlantı noktası ve komşu adresleri dışarıdan verilmek zorunda.
- Ücret oranı değişikliği yalnız 1 dağıtım birimini yeniden başlattı; diğer dört süreç hiç durmadı. Sözleşmeye dokunan bir değişiklik ise iki birimi uyumlu sırayla yayına zorlar.
- Bildirim süreci düştüğünde istek başarısız döndü, ama kitap ödünçte ve üyenin sayacı artmış kaldı: zincirin ortasında kırılan iş akışı yarım bir durum bırakıyor.
Sonraki Adım
Bu beş süreç uzun ömürlüdür: istek gelsin gelmesin ayakta durur, durumlarını bellekte tutar ve bir kez başlatılıp bırakılır. Sonraki ders dağıtım birimini bir adım daha küçültür ve uzun ömürlülüğü kaldırır: birim, yalnız bir olay tetiklediğinde var olan bir işlevdir. Ölçülecek şeyler, iki çağrı arasında bellekte hiçbir şey tutulamamasının getirdiği ek çağrılar, işlev başına konan süre sınırının iş akışını bölmesi ve süreç içinde kalmış son yerel çağrıların da ağ çağrısına dönüşmesidir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.