İçeriğe geç
academia.sh

Ders 02 / 14

Bulut Mesajlaşma

Bulut mesajlaşma servisi sunucudan aygıta iletiyi aygıt belirteciyle taşır; daraltma anahtarı ve yaşam süresi iletinin akıbetini belirler. Ders çevrimdışı aygıtta biriken iletilerden hangisinin ulaştığını ve süresi dolan iletinin teslimini ölçer.

Yayın Güncelleme

Bu derste

  • Aygıt belirteci, veri yükü, bildirim yükü, daraltma anahtarı ve yaşam süresinin bulut mesajlaşmadaki görevlerini ayırır
  • Çevrimdışı aygıtta aynı daraltma anahtarıyla biriken iletilerden yalnız sonuncusunun ulaştığını ölçer
  • Yaşam süresi aygıt çevrimiçi olmadan dolan iletinin kayıp ya da ezilen olduğunu gösterir
  • Daraltma anahtarı taşımayan iletilerin biriktikten sonra her birinin birebir ulaştığını gösterir
İçindekiler

Önceki derste olay uygulamanın kendi içinde bir kullanıcı bildirimi olarak doğuyordu. Çoğu zaman olay sunucudan çıkar: sipariş durumu değişir, yeni bir mesaj gelir, bir ödeme onaylanır. Sunucu ile aygıt arasında bir servis durur ve ileti ona emanet edilir. Aygıt kapalıysa ya da ağdan uzaksa servis iletiyi bekletir. Bu derste de aynı soruyu soruyoruz: gönderilen bu olay alıcıya kaç kopya olarak ulaştı?

// Arrival.kt — gonderilen bir olayin aliciya kac kopya olarak ulastigini siniflandirir
class Signal(val key: String, val seq: Int)

class Arrival(val label: String, val sent: Signal, received: List<Signal>) {
    val copies: Int = received.count { it.seq == sent.seq }
    val verdict: String = when {
        copies > 1 -> "yinelenen"
        copies == 1 -> "birebir"
        received.any { it.key == sent.key && it.seq > sent.seq } -> "ezilen"
        else -> "kayip"
    }

    fun line() {
        println("$label: anahtar=${sent.key} | kopya=$copies -> $verdict")
    }
}

fun countArrivals(arrivals: List<Arrival>) {
    for (name in listOf("birebir", "yinelenen", "ezilen", "kayip")) {
        println("$name: ${arrivals.count { it.verdict == name }}")
    }
}

Sınıflandırma ilk derstekiyle aynıdır. Kural alıcının listesinde aynı sırayı taşıyan kopyaları sayar ve hiç kopya yoksa aynı anahtarla daha sonraki bir olayın ulaşıp ulaşmadığına bakar. İçeriği ve nedeni görmez.

Aygıt Belirteci ve Yük

Bulut mesajlaşma servisinde sunucu iletiyi bir aygıta değil, o aygıttaki uygulamayı tanıtan aygıt belirtecine (device token) gönderir. Belirteç uygulamanın kurulumuna aittir ve zamanla yenilenebilir; uygulama yeni belirteci alınca sunucuya bildirmek zorundadır. Yenilenmeden önceki belirtece gönderilen ileti yeni kurulumda bir alıcı bulamaz.

İletinin yükü (payload) iki türdendir. Veri yükü, uygulamanın kendi anahtar ve değerlerini taşır ve uygulamanın alıcı sınıfına gelir; ne yapılacağına uygulama karar verir. Bildirim yükü ise başlık ve metin taşır ve uygulama arka plandayken platform onu kendisi bir kullanıcı bildirimi olarak gösterebilir. Önceki dersin kanalı ve izni bu ikinci yolda yine kapıdadır.

// Platform kodu: OrderMessageReceiver
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class OrderMessageReceiver : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        Log.d("siparis", "belirtec yenilendi")
        sendTokenToServer(token)
    }

    override fun onMessageReceived(message: RemoteMessage) {
        val orderId = message.data["siparis"]
        showOrder(orderId)
    }
}

Servisin alıcı sınıfı iki yöntem tanımlar. onNewToken belirteç yenilenince çağrılır, onMessageReceived veri yüküyle gelen iletiyi uygulamaya verir. Bu parça platform kodudur ve burada çıktı, süre ya da ulaşma sayısı iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Veri Yükü ve Bildirim Yükü

İki yükün farkı, iletiyi kimin işleyeceğidir. Veri yükü yalnızca anahtar ve değer çiftleri taşır; platform onu göstermeye çalışmaz, uygulamanın alıcı sınıfına verir. Uygulama o anda ne yapacağına kendi karar verir: bir kullanıcı bildirimi gösterebilir, yerel bir veritabanını güncelleyebilir ya da hiçbir şey yapmayabilir. Bildirim yükü ise başlık ve metin taşır. Uygulama arka plandayken platform bu yükü kendisi bir kullanıcı bildirimi olarak gösterebilir ve bu durumda uygulamanın kodu çalışmaz.

Bu ayrımın teslim açısından bir sonucu vardır. Veri yüküyle gelen ileti alıcıya ulaştığında alıcının kodu çalışır ve ulaşma, alıcı listesinde görünmek demektir; bu dersin modeli de ulaşmayı böyle tanımlar. Bildirim yükünün platform tarafından gösterildiği yolda ise ileti alıcının kodundan hiç geçmeyebilir. Önceki dersin kanal ve izin kapıları o yolda da geçerlidir: kapalı bir kanala ya da verilmemiş izne düşen bildirim yükü, ileti servisten çıkmış olsa bile kullanıcıya ulaşmaz. Bu yüzden “sunucu iletiyi gönderdi” ile “kullanıcı iletiyi gördü” arasında üç ayrı kapı vardır: servis, alıcı sınıfı ve kullanıcı bildirimi kapıları.

Daraltma Anahtarı

Aygıt çevrimdışıyken sunucu ileti göndermeye devam edebilir. Servis bunları aygıt için bekletir. Gönderen iletiye bir daraltma anahtarı (collapse key) koymuşsa, aynı anahtarı taşıyan bekleyen iletilerden yalnız en yenisi aygıt çevrimiçi olunca teslim edilir. Önceki durum iletileri için bu yararlıdır: sipariş üç kez güncellendiyse kullanıcının ilk ikisini görmesine gerek yoktur.

Modelde ileti Message sınıfıdır. Daraltma anahtarı varsa olayın anahtarı odur. Yoksa ileti yalnızca kendi kimliğiyle tanınır ve kimlik de anahtar olur; bu yüzden daraltılacak bir eşi yoktur.

// Message.kt — sunucunun gonderdigi iletiyi kurar, daraltma anahtari yoksa kimlik anahtar olur
class Message(val id: String, val seq: Int, val collapseKey: String? = null) {
    fun toSignal(): Signal = Signal(collapseKey ?: id, seq)
}

Aygıt çevrimdışıyken biriken iletilerde aynı anahtarlıların yalnız sonuncusu kalır.

// Offline.kt — cevrimdisi aygitta biriken iletilerden her anahtarin yalniz sonuncusunu birakir
fun collapseOffline(pending: List<Signal>): List<Signal> =
    pending.filter { s -> pending.none { it.key == s.key && it.seq > s.seq } }

Beş ileti gönderiyoruz. Sipariş durumu üç kez, sohbet mesajı ve günlük haber birer kez.

// Main.kt — cevrimdisi aygitta ayni daraltma anahtariyla biriken iletilerden yalniz sonuncusu ulasir
fun main() {
    val messages = listOf(
        Message("m1", 1, "siparis.42"),
        Message("m2", 2, "mesaj.deniz"),
        Message("m3", 3, "siparis.42"),
        Message("m4", 4, "siparis.42"),
        Message("m5", 5, "haber.gunluk"),
    )
    val sent = messages.map { it.toSignal() }
    val received = collapseOffline(sent)
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
ulasan siralar: [2, 4, 5]
olay-1: anahtar=siparis.42 | kopya=0 -> ezilen
olay-2: anahtar=mesaj.deniz | kopya=1 -> birebir
olay-3: anahtar=siparis.42 | kopya=0 -> ezilen
olay-4: anahtar=siparis.42 | kopya=1 -> birebir
olay-5: anahtar=haber.gunluk | kopya=1 -> birebir
birebir: 3
yinelenen: 0
ezilen: 2
kayip: 0

Bu dersin teslimi ezilendir. Sipariş durumunun 1 ve 3 numaralı güncellemeleri hiç teslim edilmedi, ama aynı anahtarla 4 numaralı güncelleme ulaştı ve onların yerini aldı. Kullanıcı yalnız en son durumu görür. İki ezilen olay, sipariş anahtarının üç iletisinden iki tanesidir.

K04’te ölçtüğümüz köken, bir okumanın yanıtının zincirin hangi basamağından geldiğini söylüyordu. Ezilen bir köken değildir: bir yanıtın nereden geldiğini değil, bir olayın yerini başka bir olayın aldığını söyler. Orada bir okuma hep yanıt buldu; burada bir olay hiç ulaşmayabilir.

SY1. Model, aygıtın çevrimdışı olduğu aralığı tek bir birikme anı olarak kurar: iletiler birikir ve tek hamlede teslim edilir. Platformda aygıt aralıklı bağlanabilir, iletiler birden çok kez teslim turuna girebilir ve hangi iletinin ne zaman gideceği servise aittir; bunlar modelde yoktur.

Yaşam Süresi

İletinin bir yaşam süresi (time to live, TTL) vardır. Süre dolduğunda servis iletiyi artık teslim etmez. Aygıt bu süre içinde çevrimiçi olmazsa ileti ulaşmaz. Modelde zaman gerçek saat değil, sıra numarasıyla aynı birimde bir adımdır: ileti seq adımında gönderilir, aygıt onlineAt adımında bağlanır ve yaşam süresi tamsayı bir adım sayısıdır. Bu sayılar modelin uydurma değerleridir; platformun gerçek süreleri değildir.

Aşağıdaki işlev, aygıt bağlandığında yaşam süresi dolmuş iletileri eler.

// Main.kt — yasam suresi aygit cevrimici olmadan dolan ileti ulasmaz
fun expire(sent: List<Signal>, onlineAt: Int, ttl: Int): List<Signal> =
    sent.filter { onlineAt - it.seq <= ttl }

fun main() {
    val messages = listOf(
        Message("m6", 6, "haber.spor"),
        Message("m7", 7, "siparis.43"),
        Message("m8", 8, "hatirlatma.ilac"),
        Message("m9", 9, "mesaj.nur"),
        Message("m10", 10, "siparis.43"),
    )
    val sent = messages.map { it.toSignal() }
    val received = collapseOffline(expire(sent, onlineAt = 13, ttl = 4))
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
ulasan siralar: [9, 10]
olay-6: anahtar=haber.spor | kopya=0 -> kayip
olay-7: anahtar=siparis.43 | kopya=0 -> ezilen
olay-8: anahtar=hatirlatma.ilac | kopya=0 -> kayip
olay-9: anahtar=mesaj.nur | kopya=1 -> birebir
olay-10: anahtar=siparis.43 | kopya=1 -> birebir
birebir: 2
yinelenen: 0
ezilen: 1
kayip: 2

Yaşam süresi dolan üç ileti vardı ve sonuçları farklı. 6 numaralı haber ve 8 numaralı hatırlatma kayıp oldu: ne kopyaları ne de aynı anahtarlı sonraki bir olay ulaştı. 7 numaralı sipariş ise süresi dolduğu hâlde ezilen sayıldı, çünkü aynı anahtarla 10 numaralı güncelleme ulaştı. Kural iletinin süresinin dolduğunu görmez; yalnız ulaşıp ulaşmadığına ve yerini bir olayın alıp almadığına bakar.

SY2. Model, yaşam süresini tek bir tamsayı olarak kurar ve süresi dolan iletiyi listeden çıkarır. Platformda yaşam süresinin üst sınırı servise bağlıdır ve sınırın üstündeki bir değerin nasıl ele alındığı modelde yoktur.

En Az Bir Kez Teslim

Daraltma ve yaşam süresi iletiyi azaltır. Servisin üçüncü bir davranışı iletiyi çoğaltabilir. Servis iletiyi aygıta teslim ederken bir onay bekler; onay gelmezse iletiyi yeniden gönderir. Bu düzene en az bir kez teslim denir: ileti hiç kaybolmaz ama bir defadan fazla gelebilir. Karşıtı en çok bir kez teslimdir: ileti en fazla bir defa gelir ama hiç gelmeyebilir. Servis kaybı önlemeyi seçtiği için ilkini sunar ve yinelenmenin bedelini alıcıya bırakır.

Modelde yeniden gönderim, onayı gelmemiş sıraların listenin sonuna bir kopya olarak eklenmesidir.

// Retry.kt — onayi gelmeyen iletiyi servis bir kez daha gonderir
fun retry(sent: List<Signal>, unacked: List<Int>): List<Signal> =
    sent + unacked.map { seq -> sent.first { it.seq == seq } }

Dört ileti gönderiyoruz; ikisinin onayı gelmiyor. İletilerin daraltma anahtarı yok, kimlikleri anahtar olur.

// Main.kt — onayi gelmeyen iki ileti yeniden gonderilince iki kopya olarak ulasir
fun main() {
    val messages = listOf(
        Message("m16", 16),
        Message("m17", 17),
        Message("m18", 18),
        Message("m19", 19),
    )
    val sent = messages.map { it.toSignal() }
    val received = retry(sent, unacked = listOf(17, 18))
    println("ulasan siralar: ${received.map { it.seq }}")

    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
ulasan siralar: [16, 17, 18, 19, 17, 18]
olay-16: anahtar=m16 | kopya=1 -> birebir
olay-17: anahtar=m17 | kopya=2 -> yinelenen
olay-18: anahtar=m18 | kopya=2 -> yinelenen
olay-19: anahtar=m19 | kopya=1 -> birebir
birebir: 2
yinelenen: 2
ezilen: 0
kayip: 0

17 ve 18 numaralı iletiler iki kopya olarak ulaştı ve yinelenen sayıldı; 16 ve 19 tek kopya ile birebir kaldı. Bu dersin teslimi ezilen olsa da aynı servis yinelenen de üretebilir; kategori iletiye değil, iletinin servisten geçerken yaşadığı olaya bağlıdır. Alıcının çaresi tekilleştirmedir: ileti kimliğini saklar ve ikinci gelişte işlemez. Bu, derin bağlantılar dersinde işlendi işaretiyle ayrıca ölçeceğimiz düzenin aynısıdır.

SY3. Model, onayın gelmemesini tek bir sıra için kesin ve bir kez tekrarlanan bir olay olarak kurar. Platformda yeniden gönderimin sayısı ve aralığı servise aittir ve onayın neden gelmediği modelde yoktur.

Yaygın Yanılgılar

“Daraltma anahtarı iletiyi öne alır.” Daraltma anahtarı sıralamayı ya da önceliği değiştirmez; yalnızca aynı anahtarlı bekleyen iletilerden hangisinin kalacağını belirler. Öncelik ayrı bir ayardır.

“Süresi dolan ileti yine de gecikmeli gelir.” Yaşam süresi dolduğunda servis iletiyi teslim etmez. Ölçümde 6 ve 8 numaralı iletiler bu yüzden kayıp oldu; gecikmeli gelen bir kopya yoktu. Gelmesi gereken bir ileti için yaşam süresi, aygıtın çevrimdışı kalabileceği süreden uzun seçilmelidir.

“Daraltılan ileti silinmiştir, o hâlde hata vardır.” Ezilen bir iletinin yerini aynı anahtarlı daha yeni bir ileti almıştır. Bu hata değil, gönderenin istediği davranıştır. Hata, aynı anahtarın farklı iki konuya verilmesidir: bu durumda biri gerçekten gerekli olsa bile diğeri onun yerini alır.

“Aygıt belirteci kalıcı bir kimliktir.” Belirteç, uygulama yeniden kurulduğunda ya da kullanıcı verileri temizlediğinde değişebilir. Sunucu eski belirteci saklamaya devam ederse gönderilen iletiler hiçbir aygıta ulaşmaz ve sunucu tarafında bu, başarılı gönderim gibi görünebilir. Alıcı tarafın görevi yeni belirteci sunucuya bildirmektir.

“Sunucu iletiyi gönderdiyse kullanıcı görür.” Sunucudan çıkan ileti servisten, alıcı sınıfından ve kullanıcı bildirimi kapılarından geçmek zorundadır. Kural bu kapıların hangisinde kaybolduğunu görmez; yalnızca sonucu sayar.

Sınırlayıcı Ölçüm

Daraltma her ileti için çalışmaz: yalnız daraltma anahtarı taşıyan iletileri birleştirir. Anahtarı olmayan iletiler biriktiğinde hiçbiri bir diğerinin yerini almaz.

// Main.kt — daraltma anahtari tasimayan iletiler biriktikten sonra her biri ulasir
fun main() {
    val messages = listOf(
        Message("sohbet.1", 11),
        Message("durum.1", 12, "durum.cevrimici"),
        Message("sohbet.2", 13),
        Message("durum.2", 14, "durum.cevrimici"),
        Message("sohbet.3", 15),
    )
    val sent = messages.map { it.toSignal() }
    val received = collapseOffline(sent)
    val arrivals = sent.map { Arrival("olay-${it.seq}", it, received) }
    arrivals.forEach { it.line() }
    countArrivals(arrivals)
}
olay-11: anahtar=sohbet.1 | kopya=1 -> birebir
olay-12: anahtar=durum.cevrimici | kopya=0 -> ezilen
olay-13: anahtar=sohbet.2 | kopya=1 -> birebir
olay-14: anahtar=durum.cevrimici | kopya=1 -> birebir
olay-15: anahtar=sohbet.3 | kopya=1 -> birebir
birebir: 4
yinelenen: 0
ezilen: 1
kayip: 0

Üç sohbet iletisi, kimlikleri farklı olduğu için, biriktikten sonra tek tek ve birebir ulaştı. Aynı daraltma anahtarını taşıyan iki durum iletisinden yalnız ikincisi ulaştı; ilki ezilen oldu. Sohbet için bu doğru davranıştır: her mesaj okunmak ister. Durum için de doğrudur: kullanıcının çevrimiçi olduğunu iki kez duymasının anlamı yoktur. Hangi iletiye anahtar konacağı gönderenin kararıdır, kural ise yalnız sonucu sayar.

SY4. Model, anahtarsız iletinin kimliğini benzersiz varsayar. Platformda gönderenin iki farklı iletiye aynı kimliği vermesi mümkündür ve o durumda servisin ne yapacağı modelde yoktur.

Özet

  • Bu dersin teslimi: ezilen. Çevrimdışı aygıtta aynı daraltma anahtarıyla biriken iletilerden yalnız sonuncusu ulaşır ve öncekiler yerini başka bir iletiye bırakır.
  • Aygıt belirteci uygulamanın kurulumunu tanıtır; iletinin yükü veri ya da bildirim yüküdür ve önceki dersin kanal ve izin kapıları ikinci yolda yine geçerlidir.
  • Beş iletiden ikisi ezilen oldu: sipariş durumunun ilk iki güncellemesi. Köken değil teslimdir; bir olayın yerini başka bir olay almıştır.
  • Yaşam süresi dolan iki ileti kayıp, bir ileti ezilen oldu: kayip 2, ezilen 1. Kural süre dolduğunu değil, ulaşıp ulaşmadığını görür.
  • Daraltma anahtarı taşımayan üç ileti birebir ulaştı; aynı anahtarlı iki durum iletisinden biri ezilen oldu.
  • Servis en az bir kez teslim eder: onayı gelmeyen iki ileti yinelenen oldu, yinelenen 2. Alıcının çaresi tekilleştirmedir.

Sonraki Adım

Bu derste olay sunucudan çıkıp aygıta tek bir yolla gitti. Konum sağlayıcısında olay aygıtın içinde, sürekli ve sıklıkla üretilir; alıcı hepsini işleyemeyebilir. Sıradaki ders, Konum ve Haritalar, en küçük aralık kuralının hangi konum güncellemesini düşürdüğünü ve izin geri alınınca sonraki güncellemelere ne olduğunu ölçer.

İ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