遊戲服務器的思考之二:遊戲系統的封閉性

前一篇文章講過,遊戲服務器的設計哲學是在一個封閉的環境內,針對各個環節優化以達到最大的性能。如何處置數據是達到高性能的關鍵問題,通常成熟的遊戲公司,其實不多面臨數據處理方面的壓力(如今開始流行的全球同服遊戲多是個挑戰),這是因爲他們都有一套很成熟的技術方案了。
 
咱們這個團隊因爲一開始是作web系統的,在這個基礎問題上非常掙扎了一番。下面,就咱們的經驗大致講述一下咱們對遊戲數據的分類及處置方案。
一、靜態配置數據
靜態配置數據就是指那些在運行過程當中基本不會發生變化的數據,好比角色的技能、道具屬性等,靜態配置數據推薦使用靜態文件來存儲,咱們的作法是由策劃將這些數據編寫成excel文件,技術人員經過工具腳本導出兩份,一份給客戶端使用,一份給服務器使用。具體的導出格式並不重要:咱們的項目中,前端導出的是lua腳本,後端導出的是json文件。
 
這種存儲方式的優勢:
1)讀取效率高,技術實現簡單:
就是一段讀取本地文件的代碼而已,簡單意味着不容易出錯;
2)策劃、前端、後端對數據模型達成高度的一致性:
這點對一個複雜的遊戲來講是很重要的,遊戲試圖刻畫一個虛擬的世界,再簡單的遊戲,它內部創造的概念名詞以及這些概念之間的關係,也比其餘互聯網應用多得多。一組配置文件對這個世界創建了一個靜態模型,若是你們對這個模型認識、表達不一致,結果是災難性的。相比長篇大論的需求文檔,一組配置文件要精確太多了;
3)維護修改簡單:
遊戲通常包含大量的靜態配置數據,這個數據是由策劃人員維護的,在產品的生命週期內,策劃更願意編輯靜態文件而不是一堆數據庫表;
4)方便灰度發佈
靜態文件伴隨代碼一塊兒部署,線上環境和灰度環境互不影響。
 
缺點:不支持在線修改
咱們經過技術手段實際上作到了能夠在線修改:方式是經過推送修改後的文件到服務器,再通知服務器從新加載這個文件;這個過程須要技術來執行,顯然不是一個常規操做。但靜態數據之所是靜態數據,是因爲他的本質特性決定的,試想一下角色技能應該被常常修改嗎?用戶玩遊戲的過程當中忽然多出一種從未見過技能是什麼感覺?在遊戲這個業務裏是說不通的,會破壞遊戲世界的一致性和完整性。實際的狀況下,靜態數據須要在線調整的緣由是:發現某個數值配置錯誤而進行微調,這不是一種常規的運營手段,因此「不那麼方便」是能夠接受的。
 
二、動態配置數據
配置數據整體上來講是不多發生變化的,可是咱們仍然指望某些數據在須要時能夠方便地修改(不須要技術人員的干預)。因爲這種數據能夠從外部修改,因此就必需要放在數據庫裏面了,可是因爲他不多變化,所每次都從數據庫或者緩存裏面去拉取就顯得效率過低。咱們的系統採起的是一種3級存儲的方案:數據庫+redis+服務器內存。原始數據存儲在數據庫表裏面,運營人員能夠經過GM工具對數據庫表裏面的數據進行修改,而後經過一個指令來告訴主服務器」數據發生了變化「,主服務器把數據庫表的數據加載到redis裏面,再發出一個廣播通知,全部的遊戲服務器接受到這個通知,從新從redis裏面拉取數據到內存裏。
 
動態配置數據和靜態配置數據最大的區別是:對它的修改是一種常規的運營需求,好比一個充值活動的起止時間,一個抽獎系統的機率干涉等等;二者之間有時並無明顯的界限,決策依據取決於對需求的理解,以及開發和運營之間的共識。仍是拿抽獎系統來舉例,策劃準備了兩套機率配置,但願運營依據運行的具體狀況進行調整;那麼這兩套機率都是靜態配置,那個切換的」開關「是動態配置。
 
這類數據按理不會太多,不然說明策劃的想法有問題。
 
三、全局狀態數據
有些數據是全局共享,而且變化也比較頻繁,表明遊戲運行某種全局狀態,好比博彩遊戲的總獎池狀態或者棋牌遊戲的房間列表,這些數據必須放到redis緩存系統,並進行實時的同步訪問,幸虧這樣的數據通常很少。這些數據通常不須要持久化,在服務器重啓的時候適當地初始化就好。
 
四、用戶數據
用戶數據包括基本屬性(暱稱、頭像)、財富數據(金幣、道具)、角色數據、任務狀態數據等,能夠說遊戲裏面用戶相關的數據遠遠多於web系統。
這些數據又多又讀寫頻繁,若是每次操做都訪問數據庫或緩存系統確定是妨礙性能的,所以通常採起的策略是將數據加載到服務器內存,在須要的時候才同步到緩存中心或數據庫。
 
這種延時同步的策略必然存在一致性風險:
1)不一樣服務器之間數據不一樣步:用戶進入不一樣遊戲場景會被分配到不一樣的服務器實例,系統要確保各服務器之間的用戶數據是同步的。用戶數據在某個時間段會駐留在特定的服務器實例上,要保證在切換服務器以前數據作了同步。
2)服務器內存與數據庫的不一樣步:數據庫存儲的數據狀態老是滯後的,這種滯後性並非特別大的問題(參見「遊戲系統的封閉性」),爲了減小這種滯後性對運營形成的困擾,能夠加上定時同步(週期不要太長)的機制。
3)服務器宕機致使數據丟失:定時同步能減小這種風險,在某些特定時機(好比用戶得到特別大額的金幣)進行當即同步也能減小風險。此外就只能依靠服務器本地日誌了,經過將數據變化同步地寫入日誌文件,能夠在宕機時用於恢復數據。實際上,在宕機時發生小量的數據丟失,給用戶一個統一的小額補償每每就夠了,說到底遊戲數據不是金融數據。
 
有不少遊戲系統將用戶相關的數據分紅幾塊,每塊打包成json或二進制數據塊,做爲一個字段放在數據表裏面。這種徹底不一樣於web系統的數據存儲方案是有道理的:
1)遊戲裏面數據更新雖然頻繁,但並不會實時入庫;須要入庫時,寫入效率更佳;
2)遊戲裏面涉及的數據結構特別複雜,若是採用分散字段的方式來存儲,形成表結構複雜,後期的表結構調整也是個麻煩事
3)數據庫裏面的數據能夠直接拋給客戶端;
5)數據備份更容易。
這樣的數據字段無法在外部經過sql進行讀寫和統計,你得提供一套完整的運營工具來作這些事情。有些遊戲系統會作一個折中方案,某些重要的字段獨立存儲(好比用戶ID,帳號、金幣),其餘的打包存儲(咱們的遊戲系統也在往這個方向發展)。
 
因爲歷史緣由,目前咱們的用戶數據存儲方案設計有點偏web系統,數據庫表比較多,大概可分紅3類
A、用戶屬性數據和財產數據:諸如暱稱、金幣、道具等數據
這些數據是最重要的數據,須要按期作數據庫備份,一旦出現重大漏洞和運營事故,要作數據回檔。
B、有時效的數據:好比任務、簽到等
這一類數據通常在一個固定的期限內只有一條有效記錄,能夠按時間戳來生成新的記錄,老的記錄由dba按期刪除就好(好比保留最近7天),這樣程序邏輯會比較簡單,也能保留必定的歷史記錄。
C、日誌數據:好比各類戰鬥記錄,金幣流水等
這一類數據寫入量比較大,通常單獨一個數據庫來存放,而後作按期的刪除(好比保留最近20天)。此類數據能夠用來輔助客服工做,以及在出現小的bug和運營事故時做爲補償用戶的依據。
 
五、流失用戶數據備份:
遊戲運行久了,就有不少的流失用戶,這些用戶的信息停留在系統裏面,會慢慢影響數據庫的性能,對不分服的棋牌遊戲來講,這個問題必需要處理。對於分服的遊戲來講,爲了提升硬件的利用效率,在一組服務器用戶流失多了之後,也會考慮合併多組服務器的活躍用戶數據以節省硬件資源。
 
咱們作的是棋牌遊戲,因此是不分服的,解決垃圾用戶數據問題的方案是:按期將不活躍的用戶(好比半年沒有登陸)數據,放到一個靜默用戶數據表裏面,當用戶登陸的時候先去活躍表裏面查找,若是找不到,就去靜默表查找;若是在靜默表裏面找到用戶數據,就將他恢復到在線表裏面來。按照上面的分類定義,只有A類數據須要轉移,因此須要轉移的數據實際上並不太多。
 
這樣,對活躍用戶來講,他的登陸時間是比較快的;對於新用戶來講,第一次登錄會額外增長一次在靜默表查找用戶的時間;對於流失又回來的用戶,會增長一個恢復數據的時間。爲了防止靜默表愈來愈大,能夠設定一個規則,若是用戶數據在靜默表又待了1年,那麼能夠考慮將它的數據直接刪除,另外對於身上沒有什麼財富的流失玩家,其實能夠更早一點將他刪除,好比半年甚至更短。
 
只有上面所述的A類數據是須要備份的,全部有時效的數據,咱們都不備份(咱們傾向於相信,一個靜默用戶的這類數據早就失效了)。因爲用戶的數據被存儲到多個數據表,對全部這些數據庫表分別作備份,很是繁瑣,並且一旦數據庫表調整,對應備份表的也要做調整,很是麻煩。爲了解決這個問題,咱們在備份流失用戶的數據時,將用戶全部須要備份的數據打包成一個json,存到備份表的一個字段(上面說了,有不少遊戲的用戶數據原本就是這麼存儲的,備份就很easy了)。在恢復備份數據時候,就解析這個數據塊並分別寫入不一樣的數據庫表。 
相關文章
相關標籤/搜索