Ders 25 / 25
Akış Yanıtları
Yanıtın kendisinin parça parça üretilmesi: parçalı aktarımın çerçevelemesi, ilk bayta kadar geçen sürenin ölçülmesi, başlıklar gönderildikten sonra hata bildirmenin sınırı ve akış yanıtında geri basınç.
İçindekiler
Bu konudaki dört yaklaşım da sunucudan istemciye olay taşıdı: durum değişti, sıra ilerledi, stok güncellendi. Geriye tek bir durum kalıyor. Bazen taşınacak şey bir olay değil, yanıtın kendisidir.
Otuz bin ödünç kaydını tarayan aylık rapor, sonuç tamamlanmadan da satır satır gönderilebilir. Bu, yeni bir protokol gerektirmez; HTTP yanıt gövdesinin parça parça yazılmasıyla yapılır ve karşılığında istemcinin ilk veriyi görmesi için beklediği süre belirgin ölçüde kısalır.
İki Biçim
Bir yanıt gövdesi iki biçimde gönderilebilir. Uzunluğu biliniyorsa Content-Length
başlığında bildirilir ve alıcı kaç bayt bekleyeceğini baştan bilir. Uzunluğu bilinmiyorsa
parçalı aktarım (chunked transfer encoding) kullanılır: gövde, her biri kendi
uzunluğunu taşıyan parçalara bölünür ve son parça sıfır uzunlukla akışın bittiğini
bildirir.
Aşağıdaki sunucu aynı raporu üç yolla veriyor: tamamını biriktirip gönderen /tam,
her satırı ürettiği anda yazan /akan ve üretimi yarıda bırakan /kesik.
// parcali.mjs — aylik odunc raporunu iki bicimde sunar // /tam tum satirlar bellekte toplanir, tek parca halinde gonderilir // /akan her satir uretildigi anda yazilir: parcali aktarim // /kesik ucuncu satirdan sonra uretim basarisiz olur import { createServer } from "node:http"; const SATIR = 10; const bekle = (ms) => new Promise((c) => setTimeout(c, ms)); const satirUret = (i) => `${String(i).padStart(2, "0")};sube-${1 + (i % 3)};odunc=${40 + i * 3}\n`; createServer(async (istek, yanit) => { yanit.sendDate = false; const yol = new URL(istek.url, "http://yerel").pathname; if (yol === "/tam") { // uzunluk biliniyor: tek parca const parcalar = []; for (let i = 1; i <= SATIR; i++) { await bekle(100); parcalar.push(satirUret(i)); } const govde = parcalar.join(""); yanit.writeHead(200, { "content-type": "text/csv; charset=utf-8", "content-length": Buffer.byteLength(govde) }); return yanit.end(govde); } if (yol === "/akan" || yol === "/kesik") { // uzunluk bilinmiyor: parcali aktarim yanit.writeHead(200, { "content-type": "text/csv; charset=utf-8" }); for (let i = 1; i <= SATIR; i++) { await bekle(100); if (yanit.writableEnded) return; // istemci cekildi if (yol === "/kesik" && i === 4) { // Basliklar coktan gonderildi: durum kodu artik degistirilemez. yanit.end("#hata;rapor yarida kesildi;satir=3\n"); return; } if (!yanit.write(satirUret(i))) await new Promise((c) => yanit.once("drain", c)); } return yanit.end(); } yanit.writeHead(404).end(); }).listen(8401, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8401"));
Parçalı aktarımı açıkça istemek gerekmez. Content-Length yazılmadığında ve gövde tek
çağrıda bitirilmediğinde çalışma zamanı çerçevelemeyi kendisi yapar. Yazılan tek şey
satırın kendisidir.
Akan yanıtın her yazmadan önce writableEnded durumunu sınaması, sunucu gönderimli
olaylardaki kuralın aynısıdır: istemci çekildiğinde üretimi sürdürmek boşa harcanan
işlemci ve kapalı yuvaya yazma hatası demektir.
Ölçüm
Aşağıdaki betik iki biçimi başlık, gecikme ve parça varış anları açısından karşılaştırıyor.
Ölçümü yapan zamanlayici.mjs her parçanın geliş anını yüz milisaniyelik dilimlerle
yazıyor.
// zamanlayici.mjs — akan yanitin her parcasinin varis anini 100 ms dilim olarak yazar const basla = performance.now(); let yanit; try { yanit = await fetch("http://127.0.0.1:8401/akan"); } catch { console.log("sunucuya baglanilamadi"); process.exit(0); } for await (const parca of yanit.body) { const dilim = Math.round((performance.now() - basla) / 100); process.stdout.write(`${String(dilim).padStart(2)} dilim ${Buffer.from(parca)}`); }
Bağlantı noktası 8401 keyfîdir ve boş olmalıdır.
#!/usr/bin/env bash # parcali.mjs'yi baslatir; basliklari, gecikmeleri ve parca varislerini olcer. node parcali.mjs > /dev/null & sunucu=$! sleep 1 A=http://127.0.0.1:8401 echo "--- yanit basliklari ---" for yol in tam akan; do printf '%-5s ' "$yol" curl -sS -o /dev/null -D - "$A/$yol" | grep -i -E 'content-length|transfer-encoding' | tr -d '\r' | paste -sd' ' - done echo "--- ilk bayt ve toplam sure (100 ms dilim) ---" for yol in tam akan; do curl -sS -o /dev/null -w "$yol %{time_starttransfer} %{time_total}\n" "$A/$yol" done | awk '{ printf "%-5s ilk-bayt=%2d dilim toplam=%2d dilim\n", $1, int($2*10), int($3*10) }' echo "--- akan yanitin parcalarinin varis anlari ---" node zamanlayici.mjs echo "--- ham parcali cerceveleme (ilk uc parca) ---" curl -sS --raw -N "$A/akan" 2>/dev/null | head -c 60 | od -c | head -4 echo "--- uretim yarida kesilirse ---" curl -sS -w 'durum=%{http_code}\n' "$A/kesik" kill "$sunucu"
--- yanit basliklari --- tam content-length: 190 akan Transfer-Encoding: chunked --- ilk bayt ve toplam sure (100 ms dilim) --- tam ilk-bayt=10 dilim toplam=10 dilim akan ilk-bayt= 1 dilim toplam=10 dilim --- akan yanitin parcalarinin varis anlari --- 1 dilim 01;sube-2;odunc=43 2 dilim 02;sube-3;odunc=46 3 dilim 03;sube-1;odunc=49 4 dilim 04;sube-2;odunc=52 5 dilim 05;sube-3;odunc=55 6 dilim 06;sube-1;odunc=58 7 dilim 07;sube-2;odunc=61 8 dilim 08;sube-3;odunc=64 9 dilim 09;sube-1;odunc=67 10 dilim 10;sube-2;odunc=70 --- ham parcali cerceveleme (ilk uc parca) --- 0000000 1 3 \r \n 0 1 ; s u b e - 2 ; o d 0000020 u n c = 4 3 \n \r \n 1 3 \r \n 0 2 ; 0000040 s u b e - 3 ; o d u n c = 4 6 \n 0000060 \r \n 1 3 \r \n 0 3 ; s u b --- uretim yarida kesilirse --- 01;sube-2;odunc=43 02;sube-3;odunc=46 03;sube-1;odunc=49 #hata;rapor yarida kesildi;satir=3 durum=200
Üç sonuç okunuyor.
Başlıklar biçimi ele veriyor: /tam uzunluğu bildiriyor, /akan parçalı aktarım
kullanıyor. İkisi bir arada bulunamaz.
Toplam süre iki biçimde de aynı: on dilim. Akıtmak işi hızlandırmaz, çünkü rapor yine aynı sürede üretiliyor. Değişen tek şey ilk bayta kadar geçen süredir: on dilimden bir dilime iniyor. Kullanıcı raporun ilk satırlarını, tamamının bitmesini beklemeden görüyor. Bu, gerçek zamanlı veri konusunun tamamındaki ortak kazancın burada aldığı biçimdir.
Parça varışları ilk sonucu doğruluyor: satırlar birer dilim arayla geliyor, hepsi sonda toplu olarak değil.
Çerçeveleme
Ham çıktı parçalı aktarımın biçimini gösteriyor. Her parça, uzunluğunu onaltılık
olarak yazan bir satırla başlıyor: 13 onaltılık, yani on dokuz bayt — bir rapor satırının
uzunluğu. Ardından \r\n, sonra verinin kendisi, sonra yine \r\n geliyor.
Bu çerçeveleme, uzunluk baştan bilinmediğinde alıcının gövdenin nerede bittiğini nasıl anlayacağı sorusunu çözer: son parça sıfır uzunlukla yazılır ve akış orada biter. Uzunluk bildirilmeden gövdeyi bağlantıyı kapatarak bitirmek de teknik olarak mümkündür, ama o zaman alıcı yanıtın tamamlanmış mı yoksa kesilmiş mi olduğunu ayırt edemez.
Başlıklar Gönderildikten Sonra
Son ölçüm bölümü akış yanıtlarının en önemli sınırını gösteriyor. /kesik ucunda rapor
üretimi dördüncü satırda başarısız oluyor, ama durum kodu 200 kalıyor. Çünkü başlıklar
ilk parçayla birlikte çoktan gönderildi ve HTTP’de başlıklar geri alınamaz.
Bunun üç sonucu vardır. Hata artık gövdenin içinde bildirilmek zorundadır; çıktı
biçiminin bunun için bir yeri olmalıdır — burada # ile başlayan bir bildirim satırı.
İkincisi, alıcı tarafın bu satırı okuması gerekir; okumazsa eksik raporu tam sanır.
Üçüncüsü, tamamlandığını gösteren bir bitiş imi yazmak, hata imi yazmaktan daha
güvenlidir: bağlantı kopması durumunda hata satırı da yazılamaz, ama bitiş iminin
yokluğu her durumda fark edilir.
HTTP bunun için ardıl başlıklar (trailer) da tanımlar; gövdenin ardından başlık gönderilmesine izin verirler. Uygulamada ara katman desteği düzensiz olduğu için gövde içi im daha yaygın ve daha güvenli bir seçimdir.
Akış Yanıtında Geri Basınç
Koddaki yanit.write(...) dönüşünün sınanması, geri basınç dersinde ölçülen kuralın
buradaki karşılığıdır. Ağ istemcisi yavaşsa yazılan satırlar sunucunun tamponunda birikir;
dönüş değeri yok sayılırsa tampon sınırsız büyür.
Rapor on satırlık olduğunda bu görünmez. Otuz bin satırlık bir raporu yavaş bir bağlantıya akıtan sunucu ise, dönüşü dinlemediğinde tüm raporu bellekte tutmuş olur — yani akıtmanın tek gerekçesi olan bellek kazancını da kaybeder. Akış yanıtının değeri, üretim hızının tüketim hızına bağlanmasındadır.
Ne Zaman Akıtmalı
Akıtmak her yanıt için doğru değildir. Kazanç, üretim süresi uzun ve sonucun ilk parçası tek başına anlamlıysa gerçekleşir: rapor satırları, arama sonuçları, kütük kayıtları. Küçük ve tek parça anlamlı olan yanıtlarda — bir ödünç kaydının ayrıntısı — akıtmak yalnız çerçeveleme gideri ekler.
İki pratik engel vardır. Ara katmanlar yanıtı tamponlayabilir ve akışı tek parça hâline getirebilir; bu durumda ölçülen ilk bayt süresi kazanç göstermez. Ve sıkıştırma, tampon dolmadan çıktı üretmediği için akıtmayı geciktirebilir; akış yanıtlarında sıkıştırmanın parça sınırlarında boşaltılacak biçimde ayarlanması gerekir.
Özet
- Uzunluğu bilinmeyen yanıt gövdesi parçalı aktarımla gönderilir; her parça uzunluğunu onaltılık olarak yazar ve sıfır uzunluklu parça akışı bitirir.
- Akıtmak toplam süreyi kısaltmaz; ölçümde iki biçim de on dilim sürdü, ama ilk bayta kadar geçen süre on dilimden bir dilime indi.
- Başlıklar ilk parçayla gönderildiği için durum kodu sonradan değiştirilemez; hata gövde içinde bildirilir ve bitiş imi yazmak hata imi yazmaktan güvenlidir.
- Akış yanıtında yazma dönüşü dinlenmezse gövdenin tamamı sunucu tamponunda birikir ve akıtmanın bellek kazancı ortadan kalkar.
- Akıtmanın kazancı uzun üretim süresi ile ilk parçanın tek başına anlamlı olmasına bağlıdır; ara katman tamponlaması ve sıkıştırma bu kazancı görünmez kılabilir.
Kurs Kapanışı
Bu kurs, isteğin yanıtlanma yolunu kısaltmanın ve o yolun dışına taşınan işi güvenilir kılmanın dört ayrı katmanını kurdu.
Önbellekleme hesaplanmış sonucun yeniden kullanılmasını ele aldı: istemci, kenar, sunucu ve veritabanı katmanları; yanında okuma, içinden yazma ve arkadan yazma stratejileri; süre, olay ve sürüm tabanlı geçersizleştirme; ad alanı ve kiracı ayrımı olan anahtar tasarımı; sıcak anahtar ile sürü etkisinin önlenmesi; HTTP doğrulayıcıları ve önbellek denetim yönergeleri; ve isabet oranının ölçümü. Ortak dersi şuydu: önbellek bir hızlandırıcı değil, bayatlığın denetimli kabulüdür.
Mesajlaşma işin istek yolundan çıkarılmasını kurdu: eşzamansız işlemenin gerekçesi, üretici–tüketici kuyruğu, yayımla–abone ol, akış ile kuyruk arasındaki fark, en çok bir kez ile en az bir kez ve tam bir kez teslim semantiği, sıralama ve bölümlemenin maliyeti, ölü mektup kuyruğu, zehirli mesaj ve geri çekilme. Ortak dersi teslim garantisinin bedava olmadığıydı: her güvence sıralamadan, hacimden ya da gecikmeden ödün ister.
Arka Plan İşleri mesajı işleyen tarafı bir ölçekleme birimi olarak ele aldı: işçi süreçleri ve gözetim, yineleyen görev tanımı ile çakışmayı önleyen kira kilidi, uzun süren işte ilerleme bildirimi ve işbirlikçi iptal, üretim hızının tüketim hızını aştığı yerde geri basınç, ve aşırı yükte denetimli reddetmeyi sağlayan yük boşaltma. Ortak dersi şuydu: hız uyuşmazlığı yok edilemez, yalnız nereye yazılacağı seçilebilir — belleğe, kayba ya da üreticinin hızına.
Gerçek Zamanlı Veri sonucun istemciye ulaşmasını kurdu: kısa ve uzun yoklamanın istek maliyeti, sunucu gönderimli olaylar ve olay kimliğiyle yeniden bağlanma, kendi el sıkışmasıyla kurulan WebSocket bağlantısının yaşam döngüsü, çok örnekli kurulumda kanal dağıtımı ve parça parça üretilen akış yanıtları. Ortak dersi açık bağlantının bir durum olduğuydu: taşıdığı gecikme kazancının bedeli, o durumun yönetilmesidir.
Kursun boyunca sessizce korunan bir varsayım vardı. Önbellek, kuyruk, işçi ve akış hep tek bir uygulamanın parçaları gibi ele alındı; hepsi aynı dağıtım biriminin içinde, aynı kod tabanında, aynı ekibin sorumluluğundaydı. Bu varsayım kavramları öğrenmek için gerekliydi ama gerçek sistemlerde uzun sürmez.
Sonraki kurs, Servis Mimarileri, tam olarak bu birimi bölmeyi ele alır: tek parça, modüler ve dağıtık mimariler arasında ölçüte dayalı seçim yapmayı, servis sınırlarını alan modeline göre çizmeyi ve servisler arası iletişim ile tutarlılık kalıplarını uygulamayı. Bu kursta ölçülen kavramların hiçbiri orada geçersizleşmez; hepsi bir sınırın karşısına geçer ve orada daha pahalı hâle gelir. Teslim garantisi artık bir kuyruğun değil iki ekibin sözleşmesidir. Tekillik anahtarı, ağ üzerinden gelen tekrarları da karşılamak zorundadır. Geri basınç bir süreç içinde değil, servisler arasında yayılır. Kanal dağıtımı, örnekler arasında değil sahiplikleri farklı servisler arasında yapılır. Bu kursta ölçülen her sayı, orada aynı soruların daha büyük ölçekteki karşılığını okumanın aracı olur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.