這裡資料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;
}
}
};