遊戲開發手記:數據存儲

最近主要忙着功能開發,一時感受沒什麼值得分享的。索性就數據存儲這個話題聊聊,順便本身也理一下思路,但願後面作起來能順利一點,目前咱們項目數據存儲還徹底沒作,只是簡單的把每一個玩家數據單獨存一個文件在硬盤上:-)html

關於性能

網絡遊戲的數據存儲有其特殊性。遊戲是交互性比較強的產品,對系統響應時間的要求特別高,若是在處理邏輯時同步地去進行數據庫IO是不可接受的。另外玩家在遊戲過程當中數據變化很是劇烈,也就是說寫數據的頻率會特別高,若是設計不合理,數據存儲是很容易成爲系統瓶頸的。linux

不妨拿BBS系統來作一個比較。咱們逛BBS時大部分時間都是在瀏覽,相比之下發帖行爲是小几率事件。用戶花20分鐘寫完一個帖子點擊發布按鈕,3秒鐘或10秒鐘後提示發送成功都是徹底能夠接受的,甚至提示發送失敗要求用戶從新再發一次也不是什麼大問題。因此對於上述BBS系統,在用戶提交請求時同步去讀寫數據庫是徹底可行的。而網絡遊戲要作的是同時支持數千人以數秒爲間隔不停地發貼,要保證響應時間控制在數毫秒內,還要保證發出去的帖其餘人能當即看到。程序員

業內的通行作法是將數據庫的職能儘可能簡化,只拿數據庫作數據最終的備份倉庫來用。即在服務器進程啓動時從數據庫中加載全部的全局數據,在玩家上線時從數據庫中加載玩家的全部數據,以後全部的邏輯操做都在內存中進行,從而避免遊戲過程當中讀寫數據庫帶來的響應過慢。變化的數據以必定的時間間隔(通常爲數分鐘)異步寫入數據庫,這樣就緩解了數據庫寫的壓力。數據庫

很顯然,這樣一來數據庫所須要的功能是如此簡單,只須要有GET和SET兩個接口就徹底夠用。因此選擇合適的NoSQL數據庫來提升數據存取性能也就理所固然了,必要的時候加入memcache來提升讀數據的速度也是瓜熟蒂落的事情了。這也是咱們如今徹底沒有數據庫只是存文件也能正常開發功能的緣由——說到底讀文件和寫文件也就是GET和SET操做嘛,到時候換一下接口就好了。緩存

固然了這套作法是從傳統的MMORPG演化而來的。如今大量的手遊交互性比較弱,實時性不強,用戶量小時處理邏輯時直接去讀寫數據庫每每也徹底過得去。可是在系統設計階段就把數據存儲的隱患避免掉仍是有好處的,萬一哪天遊戲火了呢?雲風的博客上有具體的案例能夠參考:談談陌陌爭霸在數據庫方面踩過的坑(排行榜篇)服務器

關於容災

丟檔多是遊戲服務器程序員永遠的噩夢,雖然沒親身經歷過,我相信不幸遭遇運營中游戲丟檔並處理過數據恢復的程序員心中應該都有不可磨滅的創傷……網絡

就像策劃每每不理解爲何寫一個100%不會崩潰的程序那麼難,軟件程序員每每也不理解爲何服務器主機會宕掉,硬盤會直接被寫廢。實際上在硬件工程師看來,電腦硬件不斷出各類問題纔是正常的吧。嗯我想說的是,咱們應該在系統設計時就應該考慮好容災,儘可能下降系統故障帶來的損失。框架

進程崩潰

根據前面的討論,爲了快速響應客戶端請求,也爲了下降數據庫寫的壓力,變化的數據並無當即寫進數據庫,而是以必定的時間間隔存盤。這個取巧的作法其實有很大的問題:咱們修改完內存中的數據後就告知客戶端操做完成,但這時數據並無成功落地,若是這時遊戲進程異常崩潰就會形成回檔,回檔的最大時長爲存盤間隔(數分鐘)。運維

那麼遊戲進程能不能不崩潰呢?有可能,可是比較難,由於遊戲進程每每複雜而且迭代很瘋狂,要100%保證不崩我以爲還得看具體的語言和框架。仍是那句話,咱們儘可能在設計階段就把風險規避掉。異步

主要思路就是用一個穩定的進程來分擔風險。前面提到能夠加入memcache進程提升讀數據的性能,很天然地咱們能夠利用memcache這個穩定的進程來暫存數據。方案是這樣的:每臺有遊戲進程運行的主機上都啓一個memcache進程並一直運行,讀數據時先從memcache讀,若讀取失敗再到數據庫讀取,當玩家數據變化時同步寫入memcache。這樣即便遊戲進程崩潰,重啓後會首先從memcache中讀出正確的數據。

往memcache寫數據通常是經過HTTP或socket,會對客戶端請求響應速度略有影響,因爲兩進程在同一主機,通常來講是能夠接受的。若是遊戲對響應速度特別敏感,能夠用共享內存的方式進行進程間通訊,前幾年在暢遊作MMORPG就是用的這種方式,只是共享內存並不易實現,程序複雜度比較高。這兩種方式沒有對錯之分,只是須要根據遊戲的需求權衡。

主機崩潰

按照以前的設計,遊戲進程和memcache都在同一主機,一旦主機崩潰不免會形成丟檔。這依舊是一個須要權衡的問題。

  1. 若是小几率的短期丟檔能夠忍受,那麼就這樣了,作好性能測試將存盤時間儘可能縮短點就行。畢竟linux是比較穩定的系統,主機崩潰並非常態。真出了問題等玩家投訴時查日誌補償回去就行了。
  2. 若是遊戲對響應速度不敏感,那麼將memcache移至另外一主機。暫且認爲兩臺主機同時崩潰的機率能夠忽略不計。
  3. 若是想一箭雙鵰,能夠考慮保留原memcache的基礎上在另外一主機上新增一級memcache。固然這樣一來系統就更復雜了,我我的並不推薦。

數據庫損壞

遊戲數據庫硬盤每每使用RAID技術,理論上幾乎不可能出現損壞。可是就怕碰上天災人禍,機房天然災害也好,程序員誤操做也好,若是不作好備份一旦出了問題絕對是致命的,直接毀掉一家公司也不是沒可能。

數據庫備份從技術上其實沒什麼好說的,就是主從備份讀寫分離什麼的。這裏我想分享以前的一點經歷。

在上家公司用的數據庫是Tokyo Cabinet,這是一款很簡潔的KV數據庫。問題是貌似不多有人用,文檔也比較匱乏,後來咱們在實際部署時不知道是bug仍是配置不對,主從數據庫老是會有些不一樣步。折騰了一段時間後咱們乾脆換了思路,不依賴主從同步機制了,直接在存盤的時候分別往兩個數據庫存盤。由於存盤過程是異步的,系統的瓶頸並不在這兒,因此存一次仍是存兩次也就無所謂了。若是對數據庫的運維不熟悉而頭疼的話能夠考慮下這個思路:-)

想象中的proxycache

遊戲服務器領域其實比較封閉,上文涉及到的NoSQL、memcache都是從Web發展出來的組件,某種程度上來講並非特別適合遊戲服務器。上面的設計中游戲進程承擔了太多數據存儲的功能,好比對玩家數據的變化一方面要同步存入memcache,一方面又要掐表計時並按間隔存入數據庫;再好比讀數據時先要去memcache中查詢,發現緩存失效時還要去數據庫中讀取。

我認爲能夠把數據存儲部分徹底從遊戲進程中抽離出來,只提供給遊戲進程GET和SET接口,這樣遊戲進程就能夠專一於處理邏輯了。抽離出來的進程也就是想象中的proxycache了,能夠用來替代前面設計中的memcache。

proxycache是兩種角色的結合體。

首先它做爲proxy是遊戲進程訪問數據庫的代理。收到GET請求時自已去向數據庫請求後轉發回遊戲進程,收到SET請求時按照必定的間隔寫入數據庫。能夠在SET參數中加入延時參數,好比不重要的數據變化能夠接受5分鐘內存盤,重要的數據變化能夠要求當即寫入數據庫。proxycache內部根據請求參數排好優先隊列後依次存儲,這樣能夠將異常宕機帶來的損失降至最低。

其次它做爲cache能緩存數據。從數據庫讀回來的數據和遊戲進程寫進來的數據均可以緩存起來,不但實現了memcache的功能還簡化了遊戲進程的邏輯。

固然瞭如今都只是紙上談兵而已,但願以後抽個空給實現出來:-)

數據庫的選擇

咱們項目數據庫徹底沒開始作的一個主要緣由是還沒選好用什麼數據庫。目前我對數據庫的指望是這樣的:首先是足夠簡單的NoSQL數據庫,最好是最簡單的Key-Value,根據咱們的設計Key只須要string,Value只須要Blob就徹底夠用了;而後但願是作爲獨立進程運行;再就是運維要方便,若是數據文件只有一個那是極好的。

先是看了Redis。坦率地說,我認爲Redis並不適合網絡遊戲。Redis原本是設計爲內存數據庫,在數據落地方面處理得比較粗糙,bgsave採用的fork方式,必須預留出足夠的內存,怎麼都以爲浪費。並且網絡遊戲運營一段時間後每每數據庫中大部分都是冷數據,Redis不分青紅皁白一股腦讀進內存實在是有些不可接受。可能仍是用做緩存比較對路吧。

再就是MongoDB。功能強大可是複雜度大大超出了咱們的指望,出了問題怕是難以駕馭。

如今主要是兩個想法:

  1. 用比較熟悉的Tokyo Cabinet,只是這個項目已經多年沒有更新了,也沒有社區什麼的。如今咱們用Go語言,本身寫個driver確定是免不了的了。
  2. rocksDB或levelDB的封裝。可選項有LedisDBSSDB,還有不少相似的,可真心無法判斷是否是靠譜ToT

各位有了解合適的嗎?求推薦!

相關文章
相關標籤/搜索