Bloga Dön

Rust'ta Edge-Native Oyun Sunucuları: Tokio'nun WASM Üzerinde Çalışması Multiplayer Mimarisi İçin Ne Anlama Geliyor?

Yayınlanma tarihi 29 Eylül 2026
Rust'ta Edge-Native Oyun Sunucuları: Tokio'nun WASM Üzerinde Çalışması Multiplayer Mimarisi İçin Ne Anlama Geliyor? Yapay zekâ yardımıyla oluşturuldu

Özet olarak

Keşfedin: Edge oyun sunucuları Rust WASM ile nasıl çalışır, Tokio'nun Emscripten hedefi multiplayer oyun mimarisinde gecikmeyi nasıl düşürür.

Multiplayer backend'iniz tek bir yerde yaşar. Muhtemelen us-east-1, ya da şanslıysanız eu-west-1. Bu bölgenin dışındaki her oyuncu bir gecikme vergisi öder — ve bu vergi her paketle birlikte artar.

São Paulo'daki bir oyuncu US-East sunucusuna bağlandığında, tek bir oyun paketi işlenmeden önce 120–150ms gidiş-dönüş süresi görür. Mumbai'deki bir oyuncu EU-West'e mi bağlanıyor? 180–220ms. Gerçek zamanlı multiplayer için bu, "duyarlı" ile "oynanamaz" arasındaki farktır.

Geleneksel çözüm pahalıdır: birden çok bölgeye dedicated server dağıtmak, geo-routing kurmak, ayrı veritabanı replikaları tutmak ve bölge başına aylık 3.000–8.000 $ bütçe ayırmak gerekir. Çoğu bağımsız stüdyo, lansman sonrası gelir hedef kitlenin varlığını kanıtlayana kadar bunu haklı çıkaramaz.

Ama yeni bir mimari olanak doğuyor. Artık Rust oyun sunucusu mantığını WebAssembly'ye derleyip dünya çapında 300'den fazla şehirdeki edge node'larda çalıştırabilirsiniz. Cloudflare, yeni bir Emscripten derleme hedefi aracılığıyla Workers platformunda Tokio tabanlı Rust uygulamalarını çalıştırmak için deneysel destek yayınladı. Multiplayer oyun backend'leri için etkileri büyük — ve bu yazı, Rust WASM'deki bir edge oyun sunucusunun pratikte neye benzediğini, nerede parladığını, nerede başarısız olduğunu ve kısıtlamalar etrafında nasıl mimari kurulacağını tam olarak açıklıyor.

Ne Değişti: Emscripten Hedefi Kütüphane Duvarını Yıkıyor

Önceden, Rust'ı edge worker'larda çalıştırmak iki kötü seçenek arasında kalmak anlamına geliyordu:

  1. wasm32-unknown-unknown — yalnızca hesaplama işlevleri için çalışır ancak yerel platform özelliklerini kaldırır. Soket yok, dosya sistemi yok, sınırlı zamanlayıcılar. Gerçek bir oyun sunucusu için kullanışsız.
  2. Native binary'ler — sizi geleneksel VM dağıtımlarıyla sınırlar, yani bölge başına veri merkezi fiyatlandırması demektir.

wasm-bindgen içindeki yeni wasm32-unknown-emscripten hedefi bunu değiştiriyor. Emscripten, yerel platform özelliklerini — zamanlayıcılar, dosya sistemi işlemleri, soketler — Web Platform API'leri üzerinde sanallaştırır. wasm-bindgen'in Rust'tan JavaScript'e interop katmanıyla birleştiğinde şunları elde edersiniz:

  • Sanallaştırılmış I/O ile tam TCP soket desteği (Cloudflare ekibi gerçek TCP soketleri kullanan bir Minecraft sunucusunu taşıdı)
  • Oyun döngüleri ve tick hızları için zamanlayıcı ve planlama desteği
  • Engelleyici olmayan işlemler için Tokio async runtime entegrasyonu
  • V8'in optimize edilmiş WebAssembly yürütmesiyle native'e yakın performans

Sonuç: normal bir Unix sisteminde çalıştığını düşünen ancak aslında yüzlerce şehre dağıtılmış edge altyapısında çalışan bir Rust oyun sunucusu. Emscripten hedefi target_family = unix bildirir, bu nedenle çoğu düşük seviyeli sistem kütüphanesi değişiklik yapılmadan çalışır.

Oyun geliştiricileri için bu, Rust WASM'deki hafif bir edge oyun sunucusunun tek bir veri merkezine dağıtım yapmadan oyuncu tabanınızın %90'ından fazlasına 10ms altı gecikmeyle oturum yönetimi, oyuncu durumu ve girdi doğrulama sunabileceği anlamına gelir.

Minimum bir edge oyun durumu işleyicisinin neye benzediği aşağıda:

use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Clone)]
struct PlayerState {
    player_id: String,
    position: [f32; 3],
    health: u16,
    last_update: u64,
}

#[derive(Serialize, Deserialize)]
struct GameState {
    players: HashMap<String, PlayerState>,
    tick: u64,
}

#[wasm_bindgen]
pub struct EdgeGameServer {
    state: GameState,
}

#[wasm_bindgen]
impl EdgeGameServer {
    #[wasm_bindgen(constructor)]
    pub fn new() -> EdgeGameServer {
        EdgeGameServer {
            state: GameState {
                players: HashMap::new(),
                tick: 0,
            },
        }
    }

    /// Update a player's position — called per input frame
    pub fn update_player(
        &mut self,
        player_id: &str,
        x: f32,
        y: f32,
        z: f32,
    ) -> String {
        self.state.tick += 1;
        let entry = self.state.players
            .entry(player_id.to_string())
            .or_insert(PlayerState {
                player_id: player_id.to_string(),
                position: [0.0, 0.0, 0.0],
                health: 100,
                last_update: 0,
            });
        entry.position = [x, y, z];
        entry.last_update = self.state.tick;

        // Return authoritative state snapshot to the client
        serde_json::to_string(&self.state).unwrap_or_default()
    }

    /// Full state snapshot for a new player joining
    pub fn get_snapshot(&self) -> String {
        serde_json::to_string(&self.state).unwrap_or_default()
    }
}

Bu, WebAssembly'ye derlenir, bir Cloudflare Worker içinde yüklenir ve yakındaki oyuncular için tek haneli milisaniye gecikmeyle oyuncu girdisini işler. wasm-bindgen katmanı Rust ile JavaScript arasında köprü kurar — Worker'ın istek işleyicisi WASM modülünü çağırır, yetkili durumu günceller ve sonucu döndürür.

Tokio Sorunu: Senkron Edge'de Async Runtime'lar

Oyun sunucuları yalnızca durum makineleri değildir. Eşzamanlı ağ I/O'sunu, zamanlayıcıları ve backend iletişimi için potansiyel TCP bağlantılarını yönetmeleri gerekir. Rust'ta bu, Tokio'nun işidir.

Ancak Cloudflare Workers — ve genel olarak edge runtime'lar — tek iş parçacıklıdır ve bir JavaScript olay döngüsü içinde barındırılır. Tokio'nun async modeli, thread'li park etme semantiği kullanan engelleyici işlemler etrafında inşa edilmiştir. Bu iki model temelde uyumsuzdur: engelleyici bir epoll_wait, paylaşılan olay döngüsünün tamamını dondurur ve o worker'daki diğer tüm istekleri durdurur.

Cloudflare mühendisleri bunu iki tamamlayıcı yaklaşımla çözdü; her ikisi de upstream Tokio'ya karşı deneysel patchset'ler olarak uygulandı.

Yaklaşım 1: WebAssembly JavaScript Promise Entegrasyonu (JSPI)

JSPI, engelleyici bir WebAssembly çağrısının yığınını askıya almasına ve kontrolü JavaScript olay döngüsüne geri vermesine izin verir. Daha önceki bir çağrı askıdayken yeni bir Wasm çağrısı girerse, JSPI ayrı bir WebAssembly yığını oluşturur — çakışmadan bir arada var olurlar.

İşin püf noktası runtime'ın iç işleyişinde. Rust'ın thread-local depolaması yığın geçişlerini bilmez. Tokio, runtime bağlamını thread local'ler üzerinden takip eder ve bir JSPI yığın geçişi bir thread geçişi değildir. Askıya alınmış iki yığın aynı thread-local durumu paylaşır ve runtime zaten girildiğini düşündüğünde panic'lere neden olur.

Çözüm, her JSPI enter, exit, suspend ve resume çağrısında işbirlikçi bağlam geçişidir — her askıya alınmış yığının kendi runtime bağlamını taşıdığı, etkili bir zaman çoklamalı thread'leme. Kırılgan ama işlevsel ve Cloudflare ekibi bunun kendi Tokio patchset'leriyle çalıştığını doğruladı.

Yaklaşım 2: Tokio için LocalEventLoop Runtime'ı

Daha genel çözüm, Tokio'nun yürütme döngüsünü iki ayrı işleme böler:

  1. drive() — bir grup için tüm hazır görevleri yoklar, ardından hemen döner
  2. wake() — host olay döngüsüne "Bekleyen işim var, hazır olduğunda drive() çağır" der

Runtime, thread'i park etmek yerine host'a sinyal verir. Host — Worker'ınızın istek işleyicisi — kendi microtask döngüsü sırasında drive() çağırır. Tokio asla engellemez; host'un zamanlamasıyla işbirliği yapar.

use tokio::runtime::LocalHandle;

/// Edge worker entry point — no blocking, cooperates with JS event loop
#[wasm_bindgen]
pub async fn handle_game_request(
    handle: &LocalHandle,
    request_body: &str,
) -> String {
    // Parse incoming player input
    let input: PlayerInput = serde_json::from_str(request_body)
        .unwrap_or_default();

    // Spawn async work on Tokio's local event loop
    // The host JS event loop drives this via wake()/drive()
    let result = handle.spawn_local(async move {
        // Async operations: DB read, backend API call, timer
        let player_data = fetch_player_profile(&input.player_id).await;
        apply_game_logic(input, player_data).await
    }).await;

    result.unwrap_or_else(|_| String::from("{\"error\":\"tick_failed\"}"))
}

LocalEventLoop tasarımı, yalnızca edge worker'lar için değil, Tokio'yu mevcut bir olay döngüsüyle entegre etmesi gereken Windows ve macOS'taki native UI uygulamaları için de genel amaçlı bir Tokio özelliği olarak önerildi. Bu genellik, upstream tarafından kabul edilme olasılığını artırıyor.

Edge Oyun Sunucuları Gerçekten Nerede Çalışır (ve Nerede Çalışmaz)

Somut olalım. Edge-native oyun sunucuları, dedicated server'ların evrensel bir alternatifi değildir. Belirli gecikmeye duyarlı iş yükleri için belirli bir araçtır.

Edge'de Başarılı Olan İş Yükleri

Oyuncu oturum yönetimi (10ms altı) Kimlik doğrulama durumu, bağlantı meta verileri ve oturum token'ları en yakın edge node'da tutulur. Bir oturum token'ını doğrulamak için kıtalararası gidiş-dönüş yoktur. 10.000 eşzamanlı oyuncuya sahip bir oyun için bu, merkezi yerine yerel olarak işlenen saniyede 10.000 oturum doğrulaması demektir.

Kart/sıra tabanlı oyunlar için hafif yetkili durum Bir kart oyununun durumu — eldeki kartlar, masa düzeni, sıra düzeni — genellikle oyuncu başına 10KB'ın altındadır. Edge node'lar bunu 5ms altı okuma gecikmesiyle korur. Hearthstone, Slay the Spire multiplayer veya herhangi bir sıra tabanlı strateji oyunu bu modele temiz bir şekilde uyar.

Varlık ve heartbeat toplama "Kim çevrimiçi?" sorguları merkezi veritabanı çağrıları yerine yerel okumalar haline gelir. Edge node'lar heartbeat'leri bölgesel olarak izler ve periyodik olarak (her 5–10 saniyede bir) küresel bir görünüme toplar.

Liderlik tablosu bölümleme Edge node'lardaki bölgesel liderlik tabloları, bir programa göre küresel sıralamalara toplanır. Oyuncular yerel sıralamalarını anında görür; küresel sıralama saniyeler içinde güncellenir. Bu, "bölgenizdeki sıralamanızın" küresel sıralama kadar önemli olduğu rekabetçi oyunlarda özellikle etkilidir.

Matchmaking durum makineleri Lobi akışı — oyuncu kuyrukları, beceri grupları, oda oluşturma — periyodik olarak senkronize olan edge-yerel durum makinelerine karşılık gelir. Kuyruk bekleme süreleri düşer çünkü matchmaking mantığı oyuncunun en yakın node'unda çalışır.

Edge'de Başarısız Olan İş Yükleri

Yetkili fizik simülasyonu 20'den fazla varlıkta çarpışma algılamayla 60Hz fizik tick'i çalıştırmak, tipik edge worker CPU bütçelerini aşar. Cloudflare Workers'ın istek başına 10–50ms CPU süresi sınırı vardır (plana bağlı olarak). Orta düzeyde karmaşık bir sahne için tek bir fizik karesi, dedicated donanımda 2–8ms sürer — güvenilir olmak için edge bütçesine çok yakındır.

Büyük dünya durumu senkronizasyonu Oyun durumunuz ~1MB'ı aşarsa, edge worker'larda serileştirme ve iletim maliyetleri engelleyici hale gelir. MMO dünya durumu, büyük araziler ve varlık yoğun sahneler, kalıcı belleğe sahip geleneksel sunuculara aittir.

Ağır frame döngülerine sahip kalıcı TCP bağlantıları Tokio Emscripten'de TCP soketlerini desteklese de, kare başına işleme (60Hz) ile uzun ömürlü bağlantıları sürdürmek edge worker ömür sınırlarını zorlar. Çoğu edge platformu, çağrı başına 30 saniye ila 5 dakikalık yürütme pencereleri uygular.

Mimari Desen: Edge + Bölgesel Hibrit

Pratik desen "tamamen edge" veya "tamamen geleneksel" değil — backend'inizi gecikmeye duyarlı ve hesaplama ağırlıklı katmanlara bölmektir.

Player Device
      │
      ▼
┌─────────────────────────────────────────┐
│         EDGE NODE (nearest city)         │
│  • Session management                    │
│  • Player state cache (read-heavy)       │
│  • Input validation & rate limiting      │
│  • Presence / heartbeat tracking         │
│  • Regional leaderboard queries          │
└──────────────────┬──────────────────────┘
                   │ (periodic sync, 1-5s)
                   ▼
┌─────────────────────────────────────────┐
│       REGIONAL GAME SERVER               │
│  • Authoritative physics / simulation    │
│  • World state management                │
│  • Heavy AI processing                   │
│  • Database writes (strong consistency)  │
└──────────────────┬──────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────┐
│       PERSISTENCE LAYER                  │
│  • Player accounts & authentication      │
│  • Inventory / progression storage       │
│  • Leaderboard persistence               │
│  • Cloud save with conflict resolution   │
└─────────────────────────────────────────┘

Edge katmanı, yakınlıktan faydalanan her şeyi yönetir: oturum doğrulama, girdi temizleme, önbelleğe alınmış oyuncu verisi okumaları ve varlık takibi. Bölgesel sunuculara periyodik olarak senkronize olur — kritik olmayan durum için her 1–5 saniyede bir, hasar veya envanter değişiklikleri gibi yetkili eylemler için anında.

Bu mimari, çoğu coğrafi bölgedeki oyuncular için yaygın işlemlerde algılanan gecikmeyi 120–200ms'den 5–15ms'ye düşürür. Rekabetçi bir multiplayer oyun için bu, "duyarlı" ile "hantal" arasındaki farktır.

Daha önce Unreal Engine multiplayer'da durum sapmasını debug ettiyseniz, net sınırlar olmadan tutarlılık modellerini karıştırmanın, yeniden üretilmesi en zor türden desync'ler yarattığını bilirsiniz. Edge-bölgesel ayrımı bu sınırları açık hale getirir: edge nihai olarak tutarlıdır, bölgesel ise yetkilidir.

Maliyet Analizi: Edge vs. Geleneksel Çok Bölgeli

10.000 eşzamanlı oyuncuya sahip bir multiplayer oyun için gerçekçi bir aylık maliyet karşılaştırması:

Altyapı Geleneksel Çok Bölgeli Edge + Bölgesel Hibrit
US-East sunucusu $400–800/mo $400–800/mo
EU-West sunucusu $400–800/mo $400–800/mo
Asia-Pacific sunucusu $400–800/mo —
Edge hesaplama (300+ şehir) — $50–200/mo
Geo-routing / DNS $50–100/mo $50–100/mo
Toplam $1,250–2,500/mo $900–1,900/mo

Edge yaklaşımı, bu oyuncuları edge node'larla kapsayarak üçüncü bir bölgesel dağıtım ihtiyacını ortadan kaldırır. 100.000 CCU'da tasarruflar daha da artar — geleneksel çok bölgeli mimari, oturum ve hafif durum iş yükleri için edge-hibrit yaklaşımdan 3–4 kat daha pahalıdır.

Edge hesaplama fiyatlandırması genellikle istek başına veya çağrı başınadır ve bu da değişken trafik desenleri için uygun maliyetli olmasını sağlar — oyun sunucularının yoğun saatler ile yoğun olmayan saatler arasında yaşadığı durumun tam olarak budur.

Bölgesel dedicated altyapınızda boşta hesaplama maliyetlerini düşürme stratejileri için, Fortnite'ın sunucu hibernasyon önerisi analizimiz, düşük trafik pencerelerinde sunucuları küçültme tekniklerini kapsar.

Edge-Native Oyun Backend'leri için 5 En İyi Uygulama

1. Durumunuzu gecikme duyarlılığına göre bölümleyin Oyun durumunun her parçası iki kategoriye ayrılır: gecikme-kritik (oyuncu konumu, girdi durumu, oturum verileri) ve tutarlılık-kritik (dünya durumu, ilerleme kayıtları, liderlik tabloları). İlk kategoriyi edge node'lara koyun. İkincisini bölgesel sunucularda veya kalıcılık katmanınızda tutun. Bir durum parçası 500ms bayatlığa dayanabiliyorsa, edge'e aittir.

2. Nihai tutarlılık için tasarlayın — ve bunun için test edin Farklı şehirlerdeki edge node'lar oyun durumu hakkında kısa süreliğine anlaşmazlık yaşayacaktır. Bunu bir tasarım kısıtı olarak kabul edin. Edge'de önbelleğe alınmış durum için son-yazı-kazanır semantiği veya CRDT'ler kullanın. İki edge node'un aynı oyuncunun girdisini eşzamanlı olarak işlemesini simüle eden ve birleştirmenin geçerli sonuçlar ürettiğini doğrulayan açık testler yazın.

#[cfg(test)]
mod consistency_tests {
    use super::*;

    #[test]
    fn two_edge_nodes_concurrent_update_merges_cleanly() {
        let mut state_a = GameState::new();
        let mut state_b = GameState::new();

        // Simulate two edge nodes processing input for same player
        state_a.update_player("player_1", 10.0, 0.0, 5.0);
        state_b.update_player("player_1", 12.0, 0.0, 3.0);

        // Merge with last-write-wins (higher tick wins)
        let merged = merge_states(&state_a, &state_b);
        let player = merged.players.get("player_1").unwrap();

        // Later tick's position should win
        assert_eq!(player.position, [12.0, 0.0, 3.0]);
    }
}

3. Edge worker yürütme süresine kesin sınırlar koyun Çoğu edge platformu, çağrı başına 30 saniye ila 5 dakika sınırı uygular. Tick mantığınızı erken profilleyin. Dedicated sunucuda 45ms süren tek bir oyun tick'i, WASM başlangıç yükü ve sanallaştırılmış I/O nedeniyle edge altyapısında 60–80ms sürebilir. Ölçümlemek için Rust'ın web_sys performans API'sini kullanın:

use web_sys::window;

fn timed_tick<F: FnOnce() -> R, R>(label: &str, f: F) -> R {
    let perf = window()
        .expect("no window")
        .performance()
        .expect("no performance API");
    let start = perf.now();
    let result = f();
    let elapsed = perf.now() - start;

    if elapsed > 50.0 {
        web_sys::console::warn_1(
            &format!("[{}] tick exceeded 50ms budget: {:.2}ms", label, elapsed).into(),
        );
    }
    result
}

4. Edge node'ları write-through cache olarak değil, read-through cache olarak kullanın Edge'den okuyun, bölgesele yazın. Edge node'lar sık okunan oyuncu verilerini — profil, loadout, son maç geçmişi — önbelleğe almalı ve yazmaları yalnızca diğer oyuncuları etkileyen veya kalıcılık gerektiren durum değişiklikleri için yetkili depolamaya iletmelidir. Bu, edge node'ları hızlı ve verilerinizi tutarlı tutar. Çoğu oyun oturumu için okuma/yazma oranı 10:1 veya daha yüksektir.

5. Edge-bölgesel senkron gecikmesini birinci sınıf bir metrik olarak izleyin Yetkili durumdan çok fazla sapan edge durumu, en kötü türden hatayı yaratır: aralıklı, coğrafyaya bağlı ve yerel olarak yeniden üretilmesi neredeyse imkansız. İlk günden itibaren senkron katmanınıza gözlemlenebilirlik ekleyin. Senkron aralıklarını izleyin, edge'de önbelleğe alınmış durum ile yetkili durum arasındaki sapmayı ölçün ve gecikme oyununuzun toleransını aştığında uyarı verin — kritik olmayan durum için genellikle 1–3 saniye, savaşla ilgili veriler için 500ms altı.

Bu, Bir Sonraki Multiplayer Projeniz İçin Ne Anlama Geliyor

Edge worker'larda Rust için Emscripten hedefi, dedicated oyun sunucularının yerine geçmez. Geleneksel çok bölgeli dağıtımların pahalı ve kusurlu şekilde ele aldığı "son mil" gecikme sorununu çözen yeni bir mimari katmandır.

Bir multiplayer oyun geliştiriyorsanız ve sunucularınıza olan gecikme oyuncu şikayetlerine neden oluyorsa — özellikle birincil sunucu bölgenizin dışındaki oyunculardan — backend'inizi bölmeyi düşünün. Oturum yönetimini, varlığı ve hafif durumu edge node'lara taşıyın. Fiziği, yapay zekayı ve dünya simülasyonunu bölgesel altyapıda tutun. Bunları periyodik bir senkron katmanıyla bağlayın.

Araç seti deneysel ama bugün işlevsel. Cloudflare'in Rust Workers örnekleri, TCP soketlerinin, Tokio async'in ve stateful Durable Objects'in birlikte çalıştığını gösteriyor. Daha fazla stüdyo benimsedikçe desenler hızla olgunlaşacaktır.

Küçük başlayın: mevcut backend'inize edge'de önbelleğe alınmış oturum doğrulama ekleyin. En uzak oyuncularınız için gecikme iyileşmesini ölçün. Rakamlar işe yararsa, daha fazla edge-native durum yönetimine genişleyin.

Bu mimarinin kalıcılık tarafı için — edge node'larınızın senkronize olduğu oyuncu kimlik doğrulama, bulut kayıtları ve liderlik tabloları — horizOn, revizyon bilincine sahip çakışma çözümüyle hesaba bağlı bulut kayıtları, cihazlar arası liderlik tabloları ve çoklu sağlayıcı kimlik doğrulama sunar. Edge sunucularınız gerçek zamanlı duruma odaklanır; horizOn bir sunucu yeniden başlatmasından sonra hayatta kalması gereken her şeyi yönetir.

Multiplayer backend'inizi küresel erişim için mimarileştirmeye hazır mısınız? Oyun durumu işlemlerinizden hangilerinin gecikmeye duyarlı, hangilerinin tutarlılık-kritik olduğunu haritalayarak başlayın. Bu tek tasarım kararı diğer her şeyi belirler.


Kaynak: Workers'ta yeni Emscripten wasm-bindgen hedefiyle native Rust desteği