İçeriğe geç
academia.sh

Ders 06 / 14

Eşitleme Stratejileri

Eşitleme stratejileri, çevrimdışıyken yapılan değişikliği kuyruğa alır, ağ dönünce sunucuya gönderir ve çakışmayı çözer. Ders çevrimdışı not düzenlemesinin ertelenen kaldığını ve depolama alanı dolunca kapandığını ölçer.

Yayın Güncelleme

Bu derste

  • Değişiklik kuyruğunun çevrimdışı yazmayı nasıl sonraya bıraktığını açıklar
  • Yinelemeye dayanıklı gönderimin neden istemci tarafından üretilen bir kimliğe dayandığını açıklar
  • Son yazan kazanır ile alan düzeyinde birleştirmenin sonuçlarını karşılaştırır
  • Çevrimdışı not düzenlemesinin ertelenen, depolama alanı dolunca kapalı çıktığını ölçer
İçindekiler

Önceki derste ağ yokken yalnız okuma ele alındı. Yerel veri gösterildi, ama kullanıcının yaptığı değişiklik hiç konuşulmadı. Gerçek bir uygulamada kullanıcı çevrimdışıyken de yazar: bir notu düzenler, bir görevi tamamlar, bir formu doldurur. Bu yazma kaybolmamalı ve ağ döndüğünde sunucuya ulaşmalıdır. Bu ders o yolculuğu üç parçaya böler: değişikliği kuyruğa almak, kuyruğu gönderirken aynı değişikliği iki kez uygulamamak ve iki tarafın aynı kaydı değiştirdiği çakışmayı çözmek.

Kursun sorusu aynı kalır: bu koşulda bu özellik ne kadar çalışıyor? Açılış dersindeki dokuz kayıt beş tam, bir kısıtlı, iki ertelenen ve bir kapalı çıkmıştı. Bu ders o sayıyı değiştirmez; yalnız kendi kesitini ölçer.

Değişiklik Kuyruğu

Çevrimdışıyken yapılan bir yazma doğrudan sunucuya gidemez. Uygulama onu yerel depoya, ayrı bir değişiklik kuyruğuna yazar. Kuyruktaki her kayıt bir değişikliği taşır: hangi kaydın hangi alanının hangi değere geçtiği ve değişikliğin kimliği. Ekran yerel veriyi hemen günceller; kullanıcı kendi değişikliğini görür. Sunucu henüz bilmez.

Kuyruk kalıcı olmalıdır. Bellekte tutulan bir kuyruk uygulama kapanınca kaybolur ve kullanıcının çevrimdışıyken yaptığı iş gider. Bu yüzden kuyruk yerel veritabanında bir tablodur. Aşağıdaki Android parçası kuyruğa kayıt eklemeyi gösterir. Parça ölçülmez.

// Platform kodu: Android degisiklik kuyrugu
class ChangeQueue(private val db: SQLiteDatabase) {
    fun enqueue(change: Change) {
        val row = ContentValues().apply {
            put("change_id", change.id)
            put("note_id", change.noteId)
            put("field", change.field)
            put("value", change.value)
        }
        db.insertOrThrow("pending_changes", null, row)
    }
}

change.id istemcide üretilir ve değişiklik gönderilene kadar sabit kalır. Bu kimliğin neden önemli olduğu birazdan görülecek. iOS tarafında aynı tablo ve aynı kimlik kuralı Swift’te kurulur; kuyruk iki platformda aynı sözleşmeyi taşır.

Notları Ölçmek

Ölçüm için kursun çekirdeği gerekir. Çekirdek ilk dersten tanıdıktır ve her derste aynı biçimde yeniden verilir; ders ona dokunmaz.

// operability.mjs — bir ozelligin bir kosulda ne kadar calistigini siniflandirir
export const LEVELS = ["tam", "kisitli", "ertelenen", "kapali"];
const FALLBACKS = ["kisitli", "ertelenen"];

export function feature(name, needs, fallback = {}) {
  return { name, needs, fallback };
}

export function condition(name, has) {
  return { name, has };
}

function judge(item, cond) {
  const missing = item.needs.filter((need) => !cond.has.includes(need)).sort();
  let level = "tam";
  for (const need of missing) {
    const given = FALLBACKS.includes(item.fallback[need]) ? item.fallback[need] : "kapali";
    if (LEVELS.indexOf(given) > LEVELS.indexOf(level)) level = given;
  }
  return { level, feature: item.name, condition: cond.name, missing };
}

export function assess(features, conditions) {
  const records = [];
  for (const cond of conditions) {
    for (const item of features) records.push(judge(item, cond));
  }
  return records;
}

export function printLevels(records) {
  for (const r of records) {
    const missing = r.missing.length === 0 ? "-" : r.missing.join(",");
    console.log(`${r.level}: ${r.feature} @ ${r.condition} eksik=${missing}`);
  }
}

export function countLevels(records) {
  for (const name of LEVELS) {
    console.log(`${name}: ${records.filter((r) => r.level === name).length}`);
  }
  console.log(`etkilenen: ${records.filter((r) => r.level !== "tam").length}`);
}

Dersin yardımcı dosyası üç özellik kurar. EditNote not düzenlemesidir ve storage ile network ister; ağ eksik olduğunda düzenleme kuyruğa alınır, bu yüzden network için yedek ertelenendir. ReadNote notu okur ve yalnız storage ister. ShareNote notu paylaşır ve yalnız network ister; yedeği yoktur. stateOf(link) bağlantı durumundan koşul üretir.

// notes.mjs — not ozelliklerini ve baglanti durumlarini kurar
import { feature, condition } from "./operability.mjs";

export const editNote = feature("EditNote", ["storage", "network"], { network: "ertelenen" });
export const readNote = feature("ReadNote", ["storage"]);
export const shareNote = feature("ShareNote", ["network"]);

export function stateOf(link) {
  const has = [];
  if (link.network) has.push("network");
  if (link.storage !== false) has.push("storage");
  return condition(link.name, has);
}

Kuyruğun modeli ikinci yardımcı dosyada durur. submit bir kaydın işlerliğine bakar ve değişikliğin akıbetini söyler: tamsa gönderilir, ertelenense kuyruğa girer, kapalıysa reddedilir. Kuyruk bir dizidir ve uzunluğu iş birimidir.

// sync.mjs — degisiklik kuyrugunu ve gonderimi kurar
export function submit(record, queue, change) {
  if (record.level === "ertelenen") {
    queue.push(change);
    return "kuyrukta";
  }
  return record.level === "tam" ? "gonderildi" : "reddedildi";
}

export function newServer(mode) {
  return { mode, applied: new Map(), log: [] };
}

export function flush(queue, server) {
  for (const change of queue) {
    if (server.mode === "anahtarli") server.applied.set(change.id, change.value);
    else server.log.push(change);
  }
  return queue.length;
}

export function stored(server) {
  return server.mode === "anahtarli" ? server.applied.size : server.log.length;
}

İlk ölçüm çevrimdışı üç düzenleme yapar ve bağlantı varken bir düzenleme yapar.

// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { editNote, readNote, stateOf } from "./notes.mjs";
import { submit } from "./sync.mjs";

const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
];
const records = assess([editNote, readNote], links.map(stateOf));
printLevels(records);
const edits = records.filter((r) => r.feature === "EditNote");
const queue = [];
console.log(`c0: ${submit(edits[0], queue, { id: "c0", value: "v-c0" })}`);
for (const id of ["c1", "c2", "c3"]) {
  console.log(`${id}: ${submit(edits[1], queue, { id, value: `v-${id}` })}`);
}
console.log(`kuyruk: ${queue.length} degisiklik`);
tam: EditNote @ online eksik=-
tam: ReadNote @ online eksik=-
ertelenen: EditNote @ offline eksik=network
tam: ReadNote @ offline eksik=-
c0: gonderildi
c1: kuyrukta
c2: kuyrukta
c3: kuyrukta
kuyruk: 3 degisiklik

Bağlantı varken düzenleme doğrudan gönderilir. Bağlantı yokken EditNote ertelenendir: üç düzenleme kuyruğa girer ve kuyruk üç değişiklik taşır. Okuma iki durumda da tamdır, çünkü yalnız yerel depoya dayanır.

CE2. Model, kuyruğu bellekteki bir dizi olarak kurar. Gerçek bir kuyruk yerel veritabanında durur, uygulama kapanıp açıldığında yaşar ve sırasını korumak zorundadır. Kuyruğun kaç değişiklik taşıdığı modelde iş birimidir; her değişikliğin ne kadar yer tuttuğu, ne kadar beklediği ve ne zaman gönderileceği modelde yoktur. Model, bu işlerliğin kuralını kurar; model çerçevenin kendisi değildir.

Yinelemeye Dayanıklı Gönderim

Ağ döndüğünde kuyruk sunucuya gönderilir. Bu adımın sorunu, gönderimin başarısını bilmenin zor olmasıdır. İstemci değişikliği gönderir, sunucu uygular, ama yanıt yolda kaybolur. İstemci değişikliğin ulaşıp ulaşmadığını bilmez ve aynı değişikliği yeniden gönderir. Sunucu ikinci kopyayı ilk kez görüyormuş gibi uygularsa değişiklik iki kez işlenir.

Çözüm değişikliği yinelemeye dayanıklı yapmaktır. Her değişiklik istemcide üretilen bir kimlikle gelir. Sunucu kimliği zaten uyguladıysa ikinci kopyayı yok sayar. Kimliğin istemcide üretilmesi şarttır: sunucu üretseydi, yanıtı kaybolan bir gönderim yeni bir kimlikle tekrarlanır ve sunucu iki ayrı değişiklik görürdü.

Aşağıdaki ölçüm aynı kuyruğu iki sunucuya iki kez gönderir. anahtarsiz sunucu her gelen değişikliği sıraya ekler. anahtarli sunucu değişikliği kimliğiyle saklar.

// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { editNote, readNote, stateOf } from "./notes.mjs";
import { submit, newServer, flush, stored } from "./sync.mjs";

const links = [
  { name: "offline", network: false },
  { name: "online", network: true },
];
const records = assess([editNote, readNote], links.map(stateOf));
const edits = records.filter((r) => r.feature === "EditNote");
printLevels(edits);
const queue = [];
for (const id of ["c1", "c2", "c3"]) submit(edits[0], queue, { id, value: `v-${id}` });
for (const mode of ["anahtarsiz", "anahtarli"]) {
  const server = newServer(mode);
  const sent = flush(queue, server) + flush(queue, server);
  console.log(`${mode}: ${sent} gonderim, sunucuda ${stored(server)} kayit`);
}
ertelenen: EditNote @ offline eksik=network
tam: EditNote @ online eksik=-
anahtarsiz: 6 gonderim, sunucuda 6 kayit
anahtarli: 6 gonderim, sunucuda 3 kayit

İki sunucuya da altı gönderim yapıldı: üç değişiklik, iki kez. anahtarsiz sunucu altı kayıt tutar; her değişiklik iki kez uygulanmıştır. anahtarli sunucu üç kayıt tutar; ikinci gönderim aynı kimlikleri bulur ve sonucu değiştirmez. Yinelemeye dayanıklılık gönderimi güvenle tekrarlanabilir kılar ve bu, yeniden denemenin bir sonraki derslerdeki dayanağıdır.

Çakışma Çözümü

Kullanıcı çevrimdışıyken sunucudaki kayıt da değişmiş olabilir; örneğin aynı not başka bir aygıttan düzenlenmiştir. Ağ dönünce iki sürüm elde bulunur ve sunucu hangisini kabul edeceğine karar vermek zorundadır. Bu karara çakışma çözümü denir ve iki yaygın strateji vardır.

Son yazan kazanır stratejisi kaydın tamamını karşılaştırır. Zaman damgası büyük olan sürüm kazanır ve ötekinin değişikliği düşer. Strateji basittir, ama farklı alanları değiştiren iki düzenlemeden birini tümüyle kaybeder. Alan düzeyinde birleştirme alanları tek tek karşılaştırır. Yalnız bir taraf bir alanı değiştirdiyse o değişiklik korunur; iki taraf da aynı alanı değiştirdiyse zaman damgası büyük olan kazanır. Aşağıdaki ölçüm bir notun başlığını sunucuda, gövdesini istemcide değiştirir ve iki stratejiyi yan yana koyar.

// merge.mjs — cakisan iki surumu iki stratejiyle birlestirir
export function lastWriterWins(local, remote) {
  return local.stamp > remote.stamp ? local.fields : remote.fields;
}

export function mergeFields(base, local, remote) {
  const merged = {};
  for (const key of Object.keys(base).sort()) {
    const localChanged = local.fields[key] !== base[key];
    const remoteChanged = remote.fields[key] !== base[key];
    if (localChanged && remoteChanged) {
      merged[key] = local.stamp > remote.stamp ? local.fields[key] : remote.fields[key];
    } else {
      merged[key] = localChanged ? local.fields[key] : remote.fields[key];
    }
  }
  return merged;
}

export function joinFields(fields) {
  return Object.keys(fields).sort().map((key) => `${key}=${fields[key]}`).join(" ");
}
// main.mjs
import { assess, printLevels } from "./operability.mjs";
import { editNote, readNote, stateOf } from "./notes.mjs";
import { lastWriterWins, mergeFields, joinFields } from "./merge.mjs";

const links = [
  { name: "offline", network: false },
  { name: "online", network: true },
];
const records = assess([editNote, readNote], links.map(stateOf));
printLevels(records.filter((r) => r.feature === "EditNote"));
const base = { title: "Liste", body: "Sut al" };
const local = { stamp: 5, fields: { title: "Liste", body: "Sut ve Cay al" } };
const remote = { stamp: 7, fields: { title: "Alisveris", body: "Sut al" } };
console.log(`son yazan kazanir: ${joinFields(lastWriterWins(local, remote))}`);
console.log(`alan duzeyi: ${joinFields(mergeFields(base, local, remote))}`);
ertelenen: EditNote @ offline eksik=network
tam: EditNote @ online eksik=-
son yazan kazanir: body=Sut al title=Alisveris
alan duzeyi: body=Sut ve Cay al title=Alisveris

Son yazan kazanır stratejisinde sunucunun sürümü kazanır ve istemcinin gövde düzenlemesi kaybolur. Alan düzeyinde birleştirmede iki değişiklik de korunur. İşlerlik kayıtları iki stratejide de aynıdır: EditNote @ offline ertelenen, EditNote @ online tamdır. Çakışmanın nasıl çözüldüğü kaydı değiştirmez, çünkü kural yetenek ve yedeği görür, iki sürümün içeriğini görmez. Strateji seçimi bir veri kaybı kararıdır ve işlerlik tablosundan değil, ürünün neyi kaybetmeyi göze aldığından çıkar.

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

Bu dersin odak kaydı EditNote @ offline’dır. Sınırlayıcı ölçüm üç özelliği üç durumda koşar. Üçüncü durum storage-full’dur: ağ vardır, depolama alanı yoktur.

// main.mjs
import { assess, printLevels, countLevels } from "./operability.mjs";
import { editNote, readNote, shareNote, stateOf } from "./notes.mjs";

const features = [editNote, readNote, shareNote];
const links = [
  { name: "online", network: true },
  { name: "offline", network: false },
  { name: "storage-full", network: true, storage: false },
];
const records = assess(features, links.map(stateOf));
printLevels(records.filter((r) => r.feature === "EditNote"));
countLevels(records);

const withServer = (mode) =>
  assess(features, [{ name: "offline", network: false, server: mode }].map(stateOf));
const same = JSON.stringify(withServer("anahtarli")) === JSON.stringify(withServer("anahtarsiz"));
console.log(`yinelenme kayda girmez: ${same ? "evet" : "hayir"}`);
tam: EditNote @ online eksik=-
ertelenen: EditNote @ offline eksik=network
kapali: EditNote @ storage-full eksik=storage
tam: 5
kisitli: 0
ertelenen: 1
kapali: 3
etkilenen: 4
yinelenme kayda girmez: evet

İlk üç satır dersin işlerliğini taşır. Aynı özellik, aynı kod: ağ varken tam, ağ yokken ertelenen. Düzenleme kaybolmaz; kuyrukta bekler ve ağ döndüğünde gönderilir. storage-full koşulunda ise kapalıdır, çünkü kuyruğun kendisi de yerel depoya yazılır. Ertelemenin yeri olan depo yoksa erteleme de yoktur. Dokuz kaydın beşi tam, biri ertelenen, üçü kapalıdır; etkilenen dört.

Son satır M30/K05’in sorusundan farkı gösterir. O ders bir işlemin yinelenirse ya da sırası değişirse sonucunun değişip değişmediğini sordu. Yinelemeye dayanıklı gönderim ile dayanıksız gönderim aynı işlerlik kaydını üretir; ama bu bir gözlem değil, modelin tanımıdır: koşulu kuran stateOf işlevi server alanını hiç okumaz, bu yüzden iki çağrı baştan aynı girdiyi görür. İşlerlik gönderimin dayanıklılığını sınıflamaz; yazmanın sonraya bırakıldığını sınıflar. Dayanıksız bir sunucu kayıtları iki kez uygular ve bu bir veri hatasıdır, ama EditNote @ offline yine ertelenen kalır.

Özet

  • Bu dersin işlerliği: ertelenen. EditNote @ offline, network eksik olduğu ve ertelenen yedeği tanımlandığı için ertelenen çıktı; aynı koşulda ReadNote tam, ShareNote kapalı kaldı.
  • Çevrimdışı yazma kalıcı bir değişiklik kuyruğuna girer; üç düzenleme üç kuyruk kaydı oldu.
  • İstemcide üretilen kimlik gönderimi yinelemeye dayanıklı yapar: altı gönderimde kimlikli sunucu üç kayıt, kimliksiz sunucu altı kayıt tuttu.
  • Son yazan kazanır kayıttaki bir düzenlemeyi kaybeder, alan düzeyinde birleştirme ikisini korur; strateji işlerlik kaydını değiştirmez.
  • storage-full koşulunda kuyruk da yazılamaz, EditNote kapanır; dokuz kaydın dördü etkilenen.

Sonraki Adım

Bu derste kuyruğun gönderimi ağ döndüğünde başlıyordu; kimin başlattığı sorulmadı. Uygulama kapalıyken ya da arka planda iken işletim sistemi bu işi çalıştırmayabilir. Sıradaki ders, Arka Plan İşleme Sınırları, iki platformun arka plan görevi düzeneğini, işletim sisteminin getirdiği sınırları ve düşük güç kipini ele alır. Sonra arka plan yeteneği kısıldığında medya yedeklemenin ne kadar çalıştığını ö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