1. 程式人生 > >遊戲服務器的思考之二:遊戲系統的封閉性

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

財富 期望 時間 編寫 棋牌遊戲 是我 決定 基本 利用

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

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