網絡遊戲中最重要的數據莫過於玩家在遊戲的過程當中產生的數據。git
能夠簡單的分紅兩類:github
第一類數據主要是相似角色『基礎』信息,揹包、技能、任務,以及全部(或者部分)玩家共有的王國、地圖、聯盟等信息。
第二類主要是相似『日誌』信息同樣的,好比「某個地方某角色使用了某道具」這樣的操做記錄。golang
這篇文章中咱們主要討論第一種數據的處理,關於後一種用做記錄和分析的數據,可能會在後面寫一個專門的Blog介紹。redis
首先咱們須要決定數據的形態,和描述的方法。
這裏有一個選擇:以『條目格式』爲核心,仍是以『文檔格式』爲核心。算法
『條目格式』的優勢:看起來較爲平坦,方便實時落地,和各類數據庫存儲模式搭配方便,外部工具修改方便,便於批量處理。
『文檔格式』的有點:一般是樹形結構,和大部分腦中的角色模型更加匹配,在內存中處理的時候,一般來講效率更高。數據庫
採用不一樣的數據結構一般會影響到項目使用的技術,甚至直接影響到總體架構和處理模型。若是使用條目,可能就會用memcache/redis存儲,使用更多的無狀態業務邏輯處理方式。json
咱們團隊習慣的,是以 struct
爲核心,配合 bson
/protobuf
實現接口和序列化,主要操做都在內存中處理的方式。
使用的時候,內存中的數據結構爲 Go 的結構體;落地時使用MongoDB
數據庫的保存的bson
格式的文檔(Document);在和移動端交互以及某些內部RPC接口調用的時候,使用protobuf
做爲傳輸格式;配置方面,使用etcd
同步的csv條目數據。服務器
不管是bson
、protobuf
仍是 csv 條目數據,均可以使用的是 Go 裏面的擴展性極強的 struct tag
來完成自動轉換。
網絡
在長時間的線上業務維護和開發過程當中,玩家的數據結構老是會修改、更新的。
若是每次都中止系統,把幾千萬甚至上億的存放在數據庫中的數據都升級準備好確定是不可能的。一般狀況下會在數據載入的過程當中進行數據兼容檢查和更新。數據結構
此時,在go
程序裏面使用bson
做爲數據的最終存儲方式會給咱們帶來很是多的便利。bson unmarshal
的自動兼容處理能夠把大部分『新增』、『刪除』操做都完美處理好,對於那些須要修改的字段或者複合結構,咱們徹底可使用新增操做來替代,升級以後刪除舊數據便可。
使用二進制數據的那種痛苦 v0.01 -> v0.02 -> v0.0N 升級過程不再須要見到了。而咱們也不須要向使用條目存儲數據的項目同樣,等待一個巨大的庫執行 alter table 的操做。
有不少人都作過測試,有不一樣的結果。咱們實際測試的結果是:
gob 最好,bson,protobuf 和 json 較差。
上面提到,須要操做的數據的,都是以 struct 的形式存放在內存中的。天然不能將全部的數據都載入,一般咱們須要維護一個數據結構,將那些好久沒有使用過的數據逐步淘汰出內存。這裏使用一個LRU表就能夠恰當且方便的完成。
Go的一個LRU實現
獲取到須要淘汰的數據,一般會將其持久化到數據庫中,線上咱們遇到過這樣的一個場景:
因此,作了LRU,最好配合限流或者hash操做,防止同時間大量數據一塊兒淘汰。
數據放在內存中,老是給人一種『不靠譜』的感受,至少是有很多人給我提起過的。
然而遊戲的業務中最重要的是什麼?我我的認爲是響應速度和可用性。
實際上,每一個半年左右總會有一些雲主機無辜掛掉(該死的阿里雲),實踐證實,經過一些常規的手段,能夠在上層避免掉軟硬件crash帶來的災難,至於內存中較新的未持久化的數據,丟了就丟了吧:
Go的動態特性(其實就是Reflect啦),給咱們帶來了一些運行時調試的便利。起碼有兩種方式能夠實現不中止服務器修改數據:
因爲Go到目前爲止(1.11)仍然沒有靠譜的動態更新方案,咱們開始考慮,在某些運營業務爲主的服務中,嘗試用lua來編寫業務邏輯,這樣能夠達到不停機修復bug甚至更新程序。