天天看點

手遊server之資料IO進化

這裡資料IO是指遊戲資料存盤和讀取。

假設IO處理不好。server在IO時會導緻。遊戲卡頓較長的時間,嚴重影響遊戲體驗。

近期服務端剛好對IO這一塊做了優化,把優化過程記錄一下。

一 原始版

剛開始立項的時候,僅僅是做了一個Demo,加上也剛開始做服務端,僅僅是做了一個僅僅可以測試用的server。

當時是在每一個場景對象area中加入了一個users對象,通過uid來儲存每一個玩家的資料。

當玩家登入的時候,将玩家的資料讀入。退出的時候将玩家的資料寫回。

var users = {};


function onLogin(uid){
    var user = DBMgr.read(uid);
    users[uid] = user;
}


function onLogout(uid){
    var user = users[uid];
    DBMgr.write(user);
    delete users[uid];
}
      

為了防止server當機資料丢失,再添加了一個定時存盤。

function onTick(){
    for (var uid in users) {
        var user = users[uid];
        DBMgr.write(user);
    }
}
      

這樣做有兩個非常明顯的問題:

1 假設一個玩家下線之後馬上又一次登入,就會又一次IO的過程;

2 每次都須要将所有玩家的資料寫入。玩家多了之後會卡非常長時間。

二 進階版

為了解決如上問題,添加了一個cache。

玩家離線後。先将其資料移到cache中,每隔一段時間,将cache中的玩家寫入存儲媒體中。

登入的時候先在cache中查找玩家的資料,假設找不到。再去讀資料。

然後結構就變成了這樣:

var users = {};
var cache = {};


function onLogin(uid){
    if (!!cache[uid]) {
        users[uid] = cache[uid];
        delete cache[uid];
    } else {
        users[uid] = DBMgr.read(uid);
    }
}


function onLogout(uid){
    var user = users[uid];
    cache[uid] = user;
    delete users[uid];
}


function onTick(){
    for (var uid in cache) {
        var user = cache[uid];
        DBMgr.write(user);
        delete cache[uid];
    }
}      

然而,前面兩個問題并沒有徹底地解決掉。

1 假設玩家下線之後。剛好onTick時間到,這樣資料就被寫回了,下次登入就得又一次讀一次;

2 若是在onTick這個周期内下線的玩家太多。onTick之中還是會有非常多玩具須要寫入。

三 終極版

為了優化前面的兩點,不再玩家的資料移到cache,而是在cache中儲存玩家的下次存盤時間。

每次登入直接在users中找資料,假設找不到,就讀資料庫。

假設玩家下線。就将其下次存盤時間在變為對應的負數,來标記玩家已經下線。

在onTick中,先将到存盤時間的玩家存盤,然後已下線的玩家從users和cache中同一時候移除。

var users = {};
var cache = {};


function onLogin(uid){
    var user = users[uid];
    if (!user) {
        user = DBMgr.read(uid);
        users[uid] = user;
        cache[uid] = curTime + WRITE_GAP;// WRITE_GAP為存盤間隔時間
    }
}


function onLogout(uid){
    cache[uid] = 0 - (curTime + WRITE_GAP);
}


function onTick() {
    for (var uid in cache) {
        var time = cache[uid];
        if (curTime < Math.abs(time)) {
            continue;
        }
        DBMgr.write(users[uid]);


        if (time < 0) { // 離線玩家
            delete users[uid];
            delete cache[uid];
        } else { // 線上玩家
            cache[uid] = curTime + WRITE_GAP;
        }
    }
};