網絡遊戲中玩家數據的處理

背景

網絡遊戲中最重要的數據莫過於玩家在遊戲的過程當中產生的數據。git

能夠簡單的分紅兩類:github

  1. 存檔數據
  2. 過程記錄數據

第一類數據主要是相似角色『基礎』信息,揹包、技能、任務,以及全部(或者部分)玩家共有的王國、地圖、聯盟等信息。
第二類主要是相似『日誌』信息同樣的,好比「某個地方某角色使用了某道具」這樣的操做記錄。golang

這篇文章中咱們主要討論第一種數據的處理,關於後一種用做記錄和分析的數據,可能會在後面寫一個專門的Blog介紹。redis

存儲結構

首先咱們須要決定數據的形態,和描述的方法。
這裏有一個選擇:以『條目格式』爲核心,仍是以『文檔格式』爲核心。算法

『條目格式』的優勢:看起來較爲平坦,方便實時落地,和各類數據庫存儲模式搭配方便,外部工具修改方便,便於批量處理。
『文檔格式』的有點:一般是樹形結構,和大部分腦中的角色模型更加匹配,在內存中處理的時候,一般來講效率更高。數據庫

採用不一樣的數據結構一般會影響到項目使用的技術,甚至直接影響到總體架構和處理模型。若是使用條目,可能就會用memcache/redis存儲,使用更多的無狀態業務邏輯處理方式。json

咱們團隊習慣的,是以 struct 爲核心,配合 bson/protobuf 實現接口和序列化,主要操做都在內存中處理的方式。
使用的時候,內存中的數據結構爲 Go 的結構體;落地時使用MongoDB數據庫的保存的bson格式的文檔(Document);在和移動端交互以及某些內部RPC接口調用的時候,使用protobuf做爲傳輸格式;配置方面,使用etcd同步的csv條目數據。服務器

不管是bsonprotobuf仍是 csv 條目數據,均可以使用的是 Go 裏面的擴展性極強的 struct tag 來完成自動轉換。
1550139780426.jpg網絡

數據兼容和升級

在長時間的線上業務維護和開發過程當中,玩家的數據結構老是會修改、更新的。
若是每次都中止系統,把幾千萬甚至上億的存放在數據庫中的數據都升級準備好確定是不可能的。一般狀況下會在數據載入的過程當中進行數據兼容檢查和更新。數據結構

此時,在go程序裏面使用bson做爲數據的最終存儲方式會給咱們帶來很是多的便利。bson unmarshal的自動兼容處理能夠把大部分『新增』、『刪除』操做都完美處理好,對於那些須要修改的字段或者複合結構,咱們徹底可使用新增操做來替代,升級以後刪除舊數據便可。

使用二進制數據的那種痛苦 v0.01 -> v0.02 -> v0.0N 升級過程不再須要見到了。而咱們也不須要向使用條目存儲數據的項目同樣,等待一個巨大的庫執行 alter table 的操做。

序列化的效率

有不少人都作過測試,有不一樣的結果。咱們實際測試的結果是:

gob 最好,bson,protobuf 和 json 較差。

LRU 的選擇

上面提到,須要操做的數據的,都是以 struct 的形式存放在內存中的。天然不能將全部的數據都載入,一般咱們須要維護一個數據結構,將那些好久沒有使用過的數據逐步淘汰出內存。這裏使用一個LRU表就能夠恰當且方便的完成。
Go的一個LRU實現

獲取到須要淘汰的數據,一般會將其持久化到數據庫中,線上咱們遇到過這樣的一個場景:

  1. 更新啓動服務器以後,停服期間玩家完成的大量操做在啓動以後統一處理(建築、戰鬥、科研等),致使大量玩家載入內存。同時觸發聯盟操做,致使大量聯盟數據也載入到相應服務進城的內存中。
  2. 因爲大部分須要持久化的數據都是使用的一個接口,裏面定義了相同的存盤間隔(或者LRU閾值)。
  3. 半小時後,巨量在啓動的時候載入的玩家和聯盟數據超過存盤閾值,被LRU算法篩選出來。
  4. 併發的序列化以及db操做致使MongoDB卡死……

因此,作了LRU,最好配合限流或者hash操做,防止同時間大量數據一塊兒淘汰。

內存數據的丟失

數據放在內存中,老是給人一種『不靠譜』的感受,至少是有很多人給我提起過的。

然而遊戲的業務中最重要的是什麼?我我的認爲是響應速度和可用性。

實際上,每一個半年左右總會有一些雲主機無辜掛掉(該死的阿里雲),實踐證實,經過一些常規的手段,能夠在上層避免掉軟硬件crash帶來的災難,至於內存中較新的未持久化的數據,丟了就丟了吧

  • 使用適當的保底存盤策略(每一個玩家半小時至少存盤一次),加上登出或者某些特殊操做(例如充值)的即時存盤,保證數據絕大部分數據的正確。
  • 敏感操做所有記錄track日誌(經過kafka一類的方式保存),必要的時候從日誌中恢復丟失的操做。
  • 使用雲服務器自帶的備份功能,將全部數據庫作1小時間隔的備份。
  • 多處引用的數據,只在一個地方保存(或者仲裁),防止部分節點數據損壞帶來的一致性問題(那怕出錯也不要衝突)。

動態讀寫內存

Go的動態特性(其實就是Reflect啦),給咱們帶來了一些運行時調試的便利。起碼有兩種方式能夠實現不中止服務器修改數據:

  • 本身實現一套get/set/del的操做,讀寫內存中的struct object。
  • 引入lua,動態的載入自定義lua script讀寫內存中的數據。

因爲Go到目前爲止(1.11)仍然沒有靠譜的動態更新方案,咱們開始考慮,在某些運營業務爲主的服務中,嘗試用lua來編寫業務邏輯,這樣能夠達到不停機修復bug甚至更新程序。

相關文章
相關標籤/搜索