İçeriğe geç
academia.sh

Ders 13 / 16

Uzun ve Kısa Yoklama

Gerçek zamanlılığın tur satın alınarak taklit edilmesi: yoklamanın protokole tek bir yeni alan eklememesi, karar başına tur maliyetinin dört yaklaşımda da değişmemesi ve harcanan tur ile tur borcunun toplamının sabit kalması.

İçindekiler

Önceki iki ders bağlantıyı açık tutan iki yol ölçtü. Tünel protokolü değiştiriyor, akış gövdeyi bitirmiyordu; ikisi de sunucunun kendiliğinden konuşabilmesi için bağlantının süresini uzatıyordu. Bu dersin sorusu şudur: hiçbirini yapmadan, protokole tek bir yeni alan bile eklemeden, yalnız daha sık sorarak nereye kadar gidilir?

Yoklama (polling), istemcinin durumu öğrenmek için düzenli aralıklarla istek göndermesidir. Kısa yoklamada sunucu hemen yanıt verir; verecek yeni bir şeyi yoksa onu söyler. Uzun yoklamada sunucu yanıtı geciktirir; bir değişiklik olana ya da tutma süresi dolana kadar isteği elinde tutar. Bu dersin ölçüsü ikisinin karar başına ödettiği turdur. Bir uyarı: önceki derste geçen yokla denetim çerçevesi (ping) ile buradaki yoklama (polling) ayrı şeylerdir; biri bağlantının ayakta olduğunu doğrulayan bir çerçeve, öbürü uygulamanın durumu öğrenmek için attığı turdur.

Aynı İstek, Farklı Zaman

İki yoklama biçimi arasındaki farkın iletide bir karşılığı yoktur.

# kısa yoklama — sunucu hemen yanıtlar
GET /olcum/kuzey HTTP/1.1
Host: istasyon.ornek
If-None-Match: "s-118"

HTTP/1.1 304 Not Modified
ETag: "s-118"

# uzun yoklama — istek satırı ve başlıkları aynı, yanıt geciktirilir
GET /olcum/kuzey HTTP/1.1
Host: istasyon.ornek
If-None-Match: "s-118"

HTTP/1.1 200 OK
ETag: "s-119"
Content-Type: application/json

{"istasyon": "kuzey-yamac", "deger": 5}

İki istek birbirinin aynıdır. Yeni bir yöntem, yeni bir başlık alanı, yeni bir durum kodu yoktur. Koşullu istek alanı da yoklamanın icadı değildir; HTTP’nin kendi alanıdır ve bu kursun HTTP ailesi konusunda kurulmuştur. Farkın tamamı sunucunun ne zaman yazdığıdır ve zaman iletide yazan bir şey değildir.

Yoklamanın sunucuya düşen maliyeti — istek sayısı, boş yanıt oranı, tutulan bağlantı — Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda ölçüldü. Burada bölen olay değil karardır ve ölçülen, aradaki aracının hesabıdır.

Ölçüm

  • DG11. Kırk tik boyunca istasyonun durumu izlenir. Bir tikte durumun değişip değişmediği kurgunun kâhinindedir; kırk tikin on ikisinde durum değişir.
  • DG12. Bir tur, istemcinin istek gönderip yanıt aldığı bir gidiş–dönüştür. Yanıtın boş olması turu ucuzlatmaz.
  • DG13. Kısa yoklama her aralık tikte bir sorar. Uzun yoklama değişiklik olana kadar tutar; tutma tik dolarsa boş yanıt verir ve istemci yeniden bağlanır.
  • DG14. Sunucu akışı ile tünel tek bir tur harcar; sonrasında aracının okuyabileceği yeni bir ileti yoktur.
  • DG15. Alınamayan her beş karar bir tur borcudur: aracı o alışverişin kararını almak isterse sunucuya dönmek zorundadır.
TOHUM = 20260809
GUVENLI = {"GET", "HEAD"}
ETKISIZ = {"GET", "HEAD", "PUT", "DELETE"}
YONTEMLER = ["GET", "GET", "GET", "HEAD", "POST", "PUT", "DELETE"]
YOLLAR = ["/olcum/kuzey", "/olcum/yamac", "/ozet", "/kayit", "/oturum"]
KARARLAR = ("saklanabilir", "paylasilabilir", "taze", "yinelenebilir",
            "yonlendirilebilir")
ACIK = ("yontem", "yol", "konak", "ozel_imi", "dogrulayici")


def uretec(tohum: int):
    d = tohum % 2147483646 + 1

    def r(n: int) -> int:
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def alisverisler(sayi: int = 40) -> list[dict]:
    r, liste = uretec(TOHUM), []
    for i in range(sayi):
        yontem, yol = YONTEMLER[r(7)], YOLLAR[r(5)]
        ozel = yol == "/oturum" or r(5) == 0
        degisti = r(3) == 0
        liste.append({"no": i + 1, "yontem": yontem, "yol": yol, "ozel": ozel,
                      "degisti": degisti, "ozel_imi": ozel and r(4) != 0,
                      "damga_atladi": degisti and r(6) == 0})
    return liste


def kahin(a: dict) -> dict:
    return {"saklanabilir": a["yontem"] in GUVENLI and not a["ozel"],
            "paylasilabilir": not a["ozel"], "taze": not a["degisti"],
            "yinelenebilir": a["yontem"] in ETKISIZ, "yonlendirilebilir": True}


def ileti(a: dict, gorunur: tuple) -> dict:
    tam = {"yontem": a["yontem"], "yol": a["yol"], "konak": "istasyon.ornek",
           "ozel_imi": a["ozel_imi"],
           "dogrulayici": a["degisti"] and not a["damga_atladi"]}
    return {k: v for k, v in tam.items() if k in gorunur}


def araci(i: dict) -> dict:
    k = {}
    if "yontem" in i:
        k["yinelenebilir"] = i["yontem"] in ETKISIZ
    if "ozel_imi" in i:
        k["paylasilabilir"] = not i["ozel_imi"]
        if "yontem" in i:
            k["saklanabilir"] = i["yontem"] in GUVENLI and not i["ozel_imi"]
    if "dogrulayici" in i:
        k["taze"] = not i["dogrulayici"]
    if "konak" in i:
        k["yonlendirilebilir"] = True
    return k


def kisa(aralik: int, liste: list[dict]) -> list[int]:
    """Her `aralik` tikte bir sorulur; yanıt o tikteki durumu döndürür."""
    return list(range(aralik - 1, len(liste), aralik))


def uzun(tutma: int, liste: list[dict]) -> list[int]:
    """Sunucu değişiklik olana kadar tutar; `tutma` tik dolarsa boş yanıt verir."""
    turlar, t = [], 0
    while t < len(liste):
        son = min(t + tutma - 1, len(liste) - 1)
        for u in range(t, son + 1):
            if liste[u]["degisti"]:
                son = u
                break
        turlar.append(son)
        t = son + 1
    return turlar


def olc(turlar: list[int], gorunur: tuple = ACIK) -> tuple:
    liste = alisverisler()
    dogru = yanlis = bayt = 0
    alanlar = set()
    for t in turlar:
        m = ileti(liste[t], gorunur)
        verilen, gercek = araci(m), kahin(liste[t])
        dogru += sum(verilen[k] == gercek[k] for k in verilen)
        yanlis += sum(verilen[k] != gercek[k] for k in verilen)
        bayt += sum(len(k) + len(str(v)) + 4 for k, v in m.items())
        alanlar |= set(m)
    alinan = dogru + yanlis
    oran = len(turlar) / alinan if alinan else float("nan")
    return (len(turlar), len(alanlar), alinan, dogru, yanlis,
            200 - alinan, oran, bayt)


LISTE = alisverisler()
print(f"tik {len(LISTE)}, karar kümesi {len(LISTE) * len(KARARLAR)}, "
      f"değişim {sum(a['degisti'] for a in LISTE)}")
print()
bicim = "{:<18s} {:>4d} {:>5d} {:>7d} {:>6d} {:>7d} {:>11d} {:>10.3f} {:>6d}"
print(f"{'yaklaşım':<18s} {'tur':>4s} {'alan':>5s} {'alınan':>7s} "
      f"{'doğru':>6s} {'yanlış':>7s} {'alınamayan':>11s} {'tur/karar':>10s} "
      f"{'bayt':>6s}")
YAKLASIMLAR = [("kısa yoklama (1)", kisa(1, LISTE), ACIK),
               ("kısa yoklama (4)", kisa(4, LISTE), ACIK),
               ("uzun yoklama (5)", uzun(5, LISTE), ACIK),
               ("sunucu akışı", [0], ACIK),
               ("tünel", [0], ())]
for ad, turlar, gorunur in YAKLASIMLAR[:-1]:
    print(bicim.format(ad, *olc(turlar, gorunur)))
n, alan, alinan, d, y, e, oran, b = olc([0], gorunur=())
print(f"{'tünel':<18s} {n:4d} {alan:5d} {alinan:7d} {d:6d} {y:7d} {e:11d} "
      f"{'-':>10s} {b:6d}")

print()
print(f"{'yaklaşım':<18s} {'harcanan tur':>12s} {'tur borcu':>9s} "
      f"{'toplam':>6s}")
for ad, turlar, gorunur in YAKLASIMLAR:
    olculen = olc(turlar, gorunur)
    harcanan, borc = olculen[0], olculen[5] // len(KARARLAR)
    print(f"{ad:<18s} {harcanan:12d} {borc:9d} {harcanan + borc:6d}")
tik 40, karar kümesi 200, değişim 12

yaklaşım            tur  alan  alınan  doğru  yanlış  alınamayan  tur/karar   bayt
kısa yoklama (1)     40     5     200    195       5           0      0.200   3524
kısa yoklama (4)     10     5      50     49       1         150      0.200    896
uzun yoklama (5)     15     5      75     72       3         125      0.200   1317
sunucu akışı          1     5       5      4       1         195      0.200     87
tünel                 1     0       0      0       0         200          -      0

yaklaşım           harcanan tur tur borcu toplam
kısa yoklama (1)             40         0     40
kısa yoklama (4)             10        30     40
uzun yoklama (5)             15        25     40
sunucu akışı                  1        39     40
tünel                         1        40     41

İlk satır kursun açık rejiminin ta kendisidir: kırk tur, 195 doğru, 5 yanlış, hiç alınamayan karar yok, 3524 bayt. Aralığı dörde çıkarmak turu ona indirir ve alınan kararı elliye düşürür. Uzun yoklama on iki değişim için on iki tur harcar, üç turu da tutma süresinin dolmasına verir: toplam on beş.

Turun Sabiti

alan sütunu dört HTTP yaklaşımında da beştir. Yoklama protokole hiçbir alan eklemez — ne kısa biçimi, ne uzun biçimi. Sunucu akışı da eklemez. Ekleyen tek şey turdur ve turun kaç tane olduğu iletide yazmaz.

tur/karar sütunu bunun doğrudan sonucudur: dört yaklaşımda da 0,200. Bu sayı 1/5’tir ve tesadüf değildir. Okunabilir bir alışveriş beş alan taşır, beş alan beş karar verir; dolayısıyla bir tur her zaman tam beş karar satın alır. Yoklamanın sıklığını değiştirmek bu oranı oynatamaz, çünkü oranın payı da paydası da aynı alan kümesinden gelir.

Buradan kursun ikinci iddiasının üçüncü biçimi çıkar. Alan eklemek karar kazandırmıyordu; alanı gizlemek kararı götürüyordu; şimdi görülüyor ki tur eklemek de karar başına maliyeti değiştirmiyor. Yoklama, gerçek zamanlılığı taklit ederken protokolden hiçbir şey almaz; yalnız aynı işlemi daha çok kez yapar.

Tünel satırı bu tablonun dışındadır ve oran hesaplanamaz: alan sıfır olduğunda bölen de sıfır olur. Bir turun satın aldığı karar sayısı sıfırsa, tur satın almanın anlamı da kalmaz.

Tur Muhasebesi

İkinci tablo aynı kırk alışverişi turun iki türüne böler. Harcanan tur, istemcinin gerçekten attığı gidiş–dönüştür. Tur borcu, aracının alamadığı kararlar için sunucuya dönmek zorunda kalacağı gidiş–dönüştür. Dört HTTP yaklaşımında toplam kırktır ve değişmez.

Bu, kursun ekseninin en yalın biçimidir. Kırk alışverişin kararı bir yerden gelmek zorundadır: ya istemci turu peşin öder ve karar iletide gelir, ya da tur borç yazılır ve karar alınamaz. Kısa yoklama borcu sıfıra indirir, kırk turu peşin öder. Sunucu akışı bir tur öder, otuz dokuz borç bırakır. Aradaki her yaklaşım bu doğrunun üzerinde bir noktadır.

Tünel satırı kırk bir verir. Fazladan bir tur, yükseltme el sıkışmasının kendisidir: bir tur harcanmıştır ve karşılığında hiçbir karar alınmamıştır. Kırk alışverişin kırkı da borç kalır. Gizlemenin bedeli, tablonun tek satır dışına taşan yerinde durur.

Tutulan İsteğin Aracıdaki Karşılığı

Uzun yoklamanın aracı için bir yan etkisi vardır. Sunucu isteği elinde tutarken bağlantı üzerinde hiçbir bayt akmaz; aracının gördüğü şey, yanıtı gecikmiş bir istektir. Aracının elinde bu bekleyişin kasıtlı olduğunu söyleyen hiçbir alan yoktur — istek satırı olağan bir GET’tir, başlıklarda “beni bekleteceksin” diyen bir alan yoktur. Önceki derste geçen boşta bekleme süresi ise bir aracının olağan kuralıdır ve tutulan isteği, bekletildiğini bilmeden keser.

  • DG16. Aracı, üzerinde bayt akmayan bir bağlantıyı boşta bekleme süresi eşiğini aştığında keser; istemci kesilen turu yeniden açar. Tablodaki boşta kesme sütunu bu eşiği tik cinsinden verir.
TOHUM = 20260809
YOLLAR = ["/olcum/kuzey", "/olcum/yamac", "/ozet", "/kayit", "/oturum"]


def uretec(tohum: int):
    d = tohum % 2147483646 + 1

    def r(n: int) -> int:
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def degisimler(sayi: int = 40) -> list[bool]:
    """Önceki bloğun üretecinden yalnız `degisti` imi alınır; çağrı sırası korunur."""
    r, liste = uretec(TOHUM), []
    for _ in range(sayi):
        r(7)                                # yöntem seçimi
        yol = YOLLAR[r(5)]
        ozel = yol == "/oturum" or r(5) == 0
        degisti = r(3) == 0
        if ozel:
            r(4)                            # kişiye özel imi
        if degisti:
            r(6)                            # damga çözünürlüğü
        liste.append(degisti)
    return liste


def uzun_turlar(tutma: int, degisti: list[bool]) -> list[tuple]:
    """Her tur (başlangıç, bitiş) tik çiftidir; bitiş ya değişim ya tutma sınırıdır."""
    turlar, t = [], 0
    while t < len(degisti):
        son = min(t + tutma - 1, len(degisti) - 1)
        for u in range(t, son + 1):
            if degisti[u]:
                son = u
                break
        turlar.append((t, son))
        t = son + 1
    return turlar


TURLAR = uzun_turlar(5, degisimler())
SURELER = [s - b + 1 for b, s in TURLAR]
print(f"uzun yoklama (tutma 5): {len(TURLAR)} tur, süreler {SURELER}")
print()
print(f"{'boşta kesme':>11s} {'kesilen tur':>11s} {'toplam tur':>10s} "
      f"{'alınan karar':>12s} {'tur/karar':>9s}")
for esik in (1, 2, 3, 5, 10):
    kesilen = sum(1 for s in SURELER if s > esik)
    toplam = len(TURLAR) + kesilen
    print(f"{esik:11d} {kesilen:11d} {toplam:10d} {toplam * 5:12d} "
          f"{toplam / (toplam * 5):9.3f}")
uzun yoklama (tutma 5): 15 tur, süreler [2, 5, 5, 3, 5, 1, 1, 2, 1, 3, 4, 4, 1, 1, 2]

boşta kesme kesilen tur toplam tur alınan karar tur/karar
          1          10         25          125     0.200
          2           7         22          110     0.200
          3           5         20          100     0.200
          5           0         15           75     0.200
         10           0         15           75     0.200

alınan karar sütunu turun beşle çarpımıdır; bir önceki ölçüm okunabilir her alışverişin tam beş karar verdiğini göstermişti. Bir tiklik bir kesme eşiği on beş turun onunu keser ve toplamı yirmi beşe çıkarır. Uzun yoklamanın kazandırdığı tur böylece geri alınır: on beş turla iş görecek bir düzen, aracının eşiği yüzünden yirmi beş tur harcar. Tutma süresini eşiğin altına çekmek çözer — beş tiklik eşikte hiçbir tur kesilmez — ama bu, uygulamanın kendi tasarımını yolun üzerindeki bir ayara göre seçmesi demektir.

tur/karar sütunu burada da 0,200’de durur. Kesilen tur da okunabilir bir alışveriştir ve o da beş karar verir. Kesme, aracının aldığı kararın oranını değiştirmez; yalnız istemcinin ödediği turu artırır.

Turla Satın Alınan Şey

Turun karar satın almadığı yerde ne satın aldığını da söylemek gerekir: tazelik. Kısa yoklamada aralık bir tik olduğunda istemci her değişimi olduğu tikte öğrenir. Aralık dört tik olduğunda en kötü durumda üç tik geç öğrenir. Uzun yoklama bu gecikmeyi tur harcamadan kapatır — sunucu değişimi beklediği için yanıt tam değişim anında yazılır — ve on beş turla kırk turun tazeliğini verir.

Bu, üç yaklaşımın gerçek ayrımıdır ve karar tablosunda hiç görünmez. Karar tablosu aracının hesabıdır; tazelik istemcinin hesabıdır. Bir protokol seçimi ikisini birden tartar: aracının kaç karar alabildiği ve istemcinin kaç turla ne kadar yeni bilgi aldığı. Ölçüm bandının alt sınırı burada da geçerlidir; iki yüz kararlık bir kümede ölçülebilen en küçük fark 1/200 = 0,0050’dir ve tablodaki farkların hepsi bunun çok üstündedir.

Özet

  • Kısa ve uzun yoklamanın istekleri birbirinin aynıdır; fark sunucunun yanıtı ne zaman yazdığındadır ve zaman iletide yazan bir alan değildir.
  • Dört HTTP yaklaşımında da aracının okuduğu alan sayısı beştir; yoklama protokole tek bir yeni alan eklemez.
  • Karar başına tur maliyeti dört yaklaşımda da 0,200’dür, çünkü okunabilir bir alışveriş her zaman tam beş karar verir; sıklığı değiştirmek bu oranı oynatmaz.
  • Harcanan tur ile tur borcunun toplamı dört HTTP yaklaşımında da kırktır: kısa yoklama 40 + 0, uzun yoklama 15 + 25, sunucu akışı 1 + 39.
  • Tünel toplamı kırk bire çıkarır; fazladan tur, karşılığında hiçbir karar alınmayan yükseltme el sıkışmasıdır.
  • Turun satın aldığı şey karar değil tazeliktir ve tazelik aracının tablosunda hiç görünmez.

Sonraki Adım

Üç yaklaşım da tek bir uygulamanın iki ucu arasındaydı ve hepsi aynı taşıma zarfını kullanıyordu: aynı konak adı, aynı bağlantı, aynı beş alan. Aracının hesabı bu yüzden tek bir sayaçta tutulabildi — hangi turun hangi alışverişe ait olduğu her zaman belliydi. Kimi protokol ise tek bir bağlantı bile kullanmaz. Sonraki ders, iletilerin birden çok bağlantıya dağıldığı bir protokol ailesini ölçer ve şu soruyu sorar: aracı gördüğü turu göremediği bir turla eşleştiremiyorsa, sayacında ne kalır?

İ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