雲風開發筆記(3) Redis, Google Protobuffer, ZeroMQ

這周的工作主要是寫代碼。

開發計劃制定好後,我們便分頭寫代碼去了。我們希望一期早點做出可以運行的東西來,一切都從簡。整體的代碼量並不多,如果硬拆成很多份讓很多人來做的話,估計設計拆分方案,安排工作,協調每個人寫的東西這些比一個人全部實現一遍的工作量還要大的多。

所以,最終就是兩個人在做。怪物公司在弄客戶端的東西,蝸牛同學包乾了服務器。好吧,基本沒我的事了,我就是那個打醬油的,好聽點說,就是設計方案。當然,事情沒多少,空下來的時間也可以幹活。訓練自己可以找到事情做,並真的做有用的事情,還是很難的。

話說回來,我們在這麼一個簡單的框架下,一開始確定了要採用一些現成的技術方案,即要用到 Redis , Google Protobuffer , ZeroMQ 。

Redis 和 ZeroMQ 是我最早選的,想了很久。


採用 Redis 是因爲歷史上,我參與的項目都沒有大規模使用 SQL 數據庫的傳統。這和 MMO 這種特定應用有關。在 Web 開發中,面對的用戶是臨時的,不依賴固定連接的。你不確定用戶在不在那裏,你不確定同時要面對的用戶有多少。你需要從小到大,採用一種可擴展容量的方案。這個時候,成熟的 SQL 數據庫方案是首選。

從軟件開發角度看,數據庫是 MVC 模式中的 M 。以 MVC 模式解決問題,M 如何實現,採用 SQL 方案只是一種可能,絕不是唯一選擇。換個角度考慮,如果是一個桌面軟件,爲什麼大部分的 M 卻沒有采用數據庫,而更多的是在內存中直接構建數據結構呢?性能恐怕只是一個原因,更重要的原因是面對的用戶的行爲不同。

爲什麼 MMORPG 服務器,至少在網易歷史上的多款遊戲,沒有使用 SQL 服務做 M 。一部分原因是,網易遊戲的開發源頭是 Mud ,Mudos 並沒有使用 SQL 作爲 M ,另一部分原因是,MMORPG 面對的,同時需要服務的用戶有限。而用戶需要操作的數據大部分限制在用戶相關的數據體內。之外的數據體非常少。及時數據總量很大,但一層索引簡單(以用戶 id 爲索引)。每個用戶都是爲它持續服務,數據易變。這種行爲下,從 MVC 角度看,更接近網絡應用之前的軟件設計。

當然你甚至可以把 M 只在邏輯上劃分出來,物理上並不切換,也就是沒有獨立意義上的數據庫服務器。這樣絕對性能最高,其實只是實現了一個簡單的單機遊戲,允許通過網絡輸入多條操作流,並把行爲反饋通過網絡發送回去罷了。甚至比單機遊戲更簡單,因爲沒有圖形控制部分。

如果程序不出問題,機房不停電,可以一直的跑下去,不用考慮數據儲存問題。數據持久化不過是爲了容災罷了。把一些結構化數據持久化到硬盤上,最簡潔的方案就是寫操作系統層面的文件,一定比再使用一個數據庫要輕量,乾淨的多。

有了以上背景,就不難理解,爲什麼我對 Redis 天生有好感。我們並沒有改變設計思路,它是一直延續下來的。Redis 幫助了我們將數據服務拆分出來。當然,MMORPG 也在發展,以上提到的用戶應用環境也在逐步變化,我們在軟件設計上也會跟進這些變化。這些就是後話了。


ZeroMQ 呢,我是希望有一個穩健,簡潔的多進程通訊方案的基礎。ZeroMQ 是不錯的一個。至少比 OS 的 Socket 庫要實用的多。它提供了更好的模式 。這是我最爲看中的。另外,這是一個 C 接口的庫,容易 binding 到不同的語言下使用。

在這個問題上,蝸牛同學是反對使用 ZeroMQ 的。對他的所有反對意見,我都持保留態度。但我尊重開發人員的意願。畢竟許多代碼不會是我自己來寫。蝸牛同學希望採用 Erlang + C Driver 的方式來驅動整個框架。也就是用 Erlang 來做通訊上的數據交換。其它可能採用的開發語言,都通過 Driver 的方式插入到 Erlang 的框架中去。

我個人覺得這樣做的確可行,加上蝸牛同學有好幾年 Erlang 開發經驗,他能擔負起實現框架的責任。我不是特別喜歡這個方案是因爲,Erlang 這個東西還是太龐大了。我對龐大的東西天生反感。雖然以蝸牛同學的原話說,Erlang 以及他的 OTP 能寫出這麼多行代碼來有他的必要性。我們用 C/C++(Python/Lua/Golang 等等) + ZeroMQ 實現一個拙劣的方案出來,只會漏洞百出。

我個人是不以爲然的,畢竟已經做了 10 年的網絡遊戲,對這個領域已經很熟悉了。對於陌生領域,我們會面對許多未知的問題;但在熟悉領域,無論怎麼做,方案都不可能太拙劣。只要保持最基本的簡潔,我是有信心保證可以解決 MMORPG 中的各種需求的。做出來的東西也能很穩定。關鍵點在於,它會足夠簡單,能輕鬆的理解實現的每一行代碼。

不過爭論都放在一邊。我的原則是,最終採用實現者自選的方案,只要它沒有明顯的問題。

我們最終不採用 ZeroMQ 。


Google Protobuffer 我不是很喜歡的。但採用它是多個角度妥協的結果。其實我更願意自定義一套協議。而只是裁剪 GPB 的功能。

我認爲,GPB 在最底層的協議編碼定義上,做的還是不錯的。改進它是多餘的。作爲一種協議定義,協議描述語言也算定義的不錯。但只到此爲止。接下來的部分就不甚滿意。

GPB 協議本身,默認也是用 GPB 本身定義編碼出來的。這看起來很 Cool ,但我不甚喜歡。當然所有完備的類似協議都應該有描述自己的能力。對於描述自己這件事情來說,GPB 還是稍顯複雜了。

比如,如果你不借助已經有的 GPB 代碼和工具,很難解析一個 GPB 協議。就好比,如果世界上第一款 C 編譯器,就是最難實現的 C 編譯器。因爲大部分的 C 編譯器是用 C 寫的,實現一個 C 編譯器,就陷入了先有雞還是先有蛋的問題。

在這類問題上,據說 Lisp 比 C 要乾的漂亮。不過我還是和世界上大多數程序員一樣,用 C 多一些。好吧,我們還是繼續用 GPB 好了。

google 在實現和使用 GPB 的時候,默認採用了一種爲每個協議,生成一組代碼的方式。而不是提供一套 C/C++ 庫,供其它語言做 binding 。這也是我所不喜的。或許是爲了性能考慮,但總覺得彆扭。如果把 GPB 換成正則表達式來看,你就能理解我的心情。

現存的大多數正則表達式的實現,都是提供一組 API ,允許使用者把需要的模式以一種人類可讀的串形式,編譯爲另一種計算機方便處理的數據結構。當你需要的時候,使用這個數據結構,交給庫,就可以匹配,替換字符串了。如果默認的選擇是把正則表達式編譯成 C 代碼,然後你用的時候再 link 到你的項目中,恐怕用的人要瘋掉了。當然,生成代碼這種可以帶來更高的運行性能。

唔,其實這只是怎樣使用 GPB 這種協議的問題,和協議定義關係不大。可惜 google 在開源之初就給出了官方的方案,引導其它語言也如法泡製,成了 GPB 的慣用法。老實說,對於 C++ ,這麼做性能是不錯的(其實也未必)。換到 Python 裏,就非常低下了。去年我按我的思路實現了 lua 的 protobuf 解析庫 ,性能可以達到和 C++ 版本差距不到一個數量級,甚至快過 java 版。

這周的剩餘時間我都在寫一個純粹的 C 版的 protobuf 庫,不依靠代碼生成器的。希望能夠作爲它語言使用 GPB 的基礎。別的語言只需要做 binding 就夠了。這玩意挺難寫,光接口設計我就改了兩版。今天終於快收工了。過兩天再寫一篇文章專門談談這個問題吧。當然,還有開源。

既然在 GPB 上花了這麼多功夫,當然,採用 GPB 就是最後的決定了。

發佈了16 篇原創文章 · 獲贊 9 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章