談談陌陌爭霸在數據庫方面踩過的坑(前篇)
陌陌爭霸 這個項目一開始不叫這個名字,它在 2013 年中的時候,還只是一個我們公司 用來試水移動游戲的試驗項目。
最開始的目標很明確,COC 是打動我的第一款基于移動平臺網絡游戲,讓我看到了和傳統 MMO 不同的網絡游戲設計方向。我覺得只需要把其中最核心的部分剝離出來,我們很快可以做出一個簡單的卻不同于以往 MMO 的游戲,然后就可以著手在此基礎上發展。
至于后來找到陌陌合作,是個機緣巧合的故事。
我們的試驗項目完成,卻沒想好怎么推給玩家去玩(而這類游戲沒有一定的玩家群體基本玩不起來),而陌陌 游戲平臺剛上線,僅有的一款產品(類似泡泡龍的游戲)成績不佳。因為我們公司和陌陌的創始人都曾經在網易工作,非常熟悉。這款游戲也就只花了一個月時間就 在陌陌游戲平臺發布了。
一開始我們只把剛完成狂刃 啟動器項目的阿楠調過來換掉我來做這個項目,我在做完了初期的圖形引擎工作后,就把游戲的實現交給了他。我們只打算做客戶端,因為只有這部分需要重新積累技術經驗;而服務器不會和傳統 MMO 有太大的不同。而我們公司已經圍繞 skynet 這套服務器框架開發有很長一段時間了,隨時都可以快速把這個手游項目的服務器快速搭建起來。
到 2013 年夏天,感覺應該開始動手做服務器部分了。曉靖在斗羅大陸的端游項目中積累了不少服務器開發的經驗,也是除我之外,對 skynet 最為熟悉的人;如果這個試驗項目只配備一個程序來開發服務器的話,沒有更好的人選了。
從那個時候起,我們開始考慮服務器的結構,其中也包括了數據庫的選型和構架。
skynet 有自己的 IO 模型,如果要足夠高效,最好是能用 skynet 提供的 socket 庫自己寫 DB 的 driver 。因為 redis 的協議最簡潔,所以最先我只給 skynet 制作了 redis 的 driver 。而我們代理的游戲狂刃的開發方使用的是 MongoDB ,為了運營方便,我們的平臺也使用它做了不少東西,我便制作給 skynet 制作了初步的 mongodb driver 。到服務器開始開發時,我們有了兩個選擇。
十多年的游戲行業從業經驗告訴我,數據庫在實時交互性較強的在線游戲中,主要起的是一個數據備份容災的作用。很少會將其用于數據交換。而在其它領域,很多開發者則選擇把數據庫作為業務模塊間的數據交換,帶著這個思路來做游戲,往往會帶來很嚴重的性能問題。
簡單說,理論上,由于游戲服務器往往 7 * 24 小時持續工作,且玩家具有強交互性,大部分游戲世界里的數據都一直存在于內存中。當服務器啟動后,一旦數據加載完畢,大部分不再需要退出內存。服務器只是 在不斷的創造新數據并讓這些數據在內存中流通而已,它沒有任何需要從外部讀取數據。如果內存無限大,且服務器永遠不會當機,數據庫這個設施沒有存在的必要。
當然這兩個前提條件都不可能成立。
對于內存無限大這個條件,傳統 MMORPG 游戲需要消耗的內存是 O(n) 的,n 和總用戶數相關。雖然同時玩游戲的用戶數(活躍用戶數)有限,很難持續增長;但總用戶數的確是隨時間增長的。我們只要把 n 從總用戶數變成活躍用戶數后,基本就能維持內存的需求。
最簡單的做法是,當一個用戶不活躍后,就把這部分數據落地(寫入磁盤),當他有一天又變得活躍后,再從磁盤加載回來。在端游早期,用戶活躍的標準就 是他是否在線。我們在用戶上線的時候加載他的數據,離線的時候將數據落地即可。從開發角度看,數據如何保存,最簡單的方法不是使用數據庫,而是以用戶 id 為文件名,把用戶數據序列化成文本寫入文件系統即可。這也就是網易早期游戲的通用做法。
對于服務器穩定性的要求,我們不可能作到 100% 不當機,所以數據還是要定期存盤的。可以是按時間為周期保存,也可以是在關鍵操作發生時保存。這樣在災難發生的時候可以恢復回來。
btw, 一個系統所需要管理的數據總量小于系統總的內存量這一點,不僅僅在游戲領域,其實很多別的系統也存在。所以 redis 這種純內存數據庫才有了廣泛的應用空間。redis 的 BGSAVE 以及 BGSAVE 的兩種模式,也對應了上面所指的數據落地策略。
至于,如何操作這些數據的問題,既然數據都在你系統的內存中,總可以寫出對應的算法去處理它們吧?明白了這一點,就能明白為什么在大多數在線游戲系統中,選用怎樣的數據庫就不是什么重要的問題了。
當然,一個在線游戲的運營還是需要大量的游戲內數據分析的。本著不同的業務邏輯盡量分離的原則,我們還是需要把游戲內的數據輸出出來,交給專業的系統,專業的人來處理。這一部分的數據量遠大于游戲系統為玩家服務時所需要的量。我認為它的空間復雜度是 O(n * m) 的。其中有兩個維度,一是玩家的總數,二是運營的時間。游戲服務器需要把運營過程中的數據吐出,保存到可以處理這么大數據量的數據庫中去。我們把這部分數 據稱為運營 log ,這個名稱我覺得不太合適,因為它容易和程序輸出的供調試分析的錯誤 log 相混淆,不過歷史上在網易工作時大家都這么叫,我也不打算起個新名詞了。
陌陌爭霸在服務器方面的選型和構架按著這個思路做出來:
我們用 redis 保存玩家的數據,考慮到玩家數量可能很多,一個 redis 倉庫可能不夠,我們使用了 32 個 redis 倉庫,按玩家 id 分開存放。在部署方面,可以在用戶數量較少的時候,把多個 redis 倉庫部署在同一臺物理機上,再隨著用戶規模擴大而分開部署。如果 32 個倉庫不夠的話,進一步細分也不會是難事。
在前三個月,我們不用太考慮冷熱數據的問題,這個期間還談不上流失玩家,所有玩家數據都是熱數據。由于開發時間緊迫,我們把冷數據處理留到后期再處理。
至于數據落地的問題,redis 已有 bgsave 的能力,我們只需要細調就好了。
而運營 log 和一些隨時間自然增長的數據,比如戰斗錄像,我們選擇了不受內存限制,且易于做數據分析的 mongodb 。由于擔心數據量過大,使用了 mongos 分片。
初期的設計就是這樣了,只到今天,也沒有在結構上做什么調整。但是在操作過程中踩了許多坑,都是值得好好記錄下來的經驗。
預告:陌陌游戲平臺的第二款游戲:陌陌勁舞團先于陌陌爭霸半個月上線。上線后不到兩天就宣布停服,停服時間一再延長,一直拖了一周。傳言說問題就出在數據庫這塊。下篇打算八卦一下這個事情。
原文地址。51CTO經作者授權轉載。