過去一年中,花了很多時間在考慮伺服器架構設計方面的問題。看了大量文章、也研究了不少開源項目,眼界倒是開闊了不少,不過回過頭來看,對網遊架構設計方面的協助卻是不多。老外還是玩兒console game的多,MMO Games方面涉及的還是不如國內廣泛。看看 Massively Multiplayer Games Development 1 & 2 這兩本書吧,品質說實話很一般,協助自然也很有限。當然這也是好事,對國內的研發公司/團隊來說,在網遊伺服器技術方面當然就存在超越老外的可能性,而且在這方面技術超越的機會更大,當然前提是要有積累、要捨得投入,研發人員更要耐得住寂寞、經得起誘惑,在平均每天收到超過3個獵頭電話的時候——依然不動心。
上面有點兒扯遠了,下面聊聊無縫世界架構(Seamless world server architecture)設計方面的一點兒看法。
先說架構設計的目標——我的看法,伺服器組架構設計的目標就是確定各伺服器拓補關係和主要的商務邏輯處理方法。主要要解決的問題就是在滿足遊戲內容設計需要的前提下,如何提高帶負載能力的問題。
最簡單的架構就是基本的C/S架構,一台Server直接構成一個Cluster,所有Client直接連接這個Server,這個Server完成所有邏輯和資料處理。這架構其實很好,最大的好處就是它架構上的 Simplicity ,Cluster內部的跨進程互動完全被排除,複雜度立刻就降下來了,而且——完全可以實現一個無縫(Seamless world)的遊戲世界。但是即使我不說,大家也知道這種單Server架構會有什麼問題。不過我們不妨以另外一個角度來看這個Server——一個黑盒子。從系統外部的角度來看,什麼樣的系統都可以看成一個整體、一個黑盒,而不管系統內部的拓補關係和實現複雜度方面的問題。在不考慮這個系統的實現的前提下,理論上Cluster的處理能力就是由硬體的數量和能力決定的,也就是說一個Server Cluster內包含越多的伺服器、伺服器越‘快’,那麼這個Cluster的處理能力越好、帶負載能力越好。那麼我們要面對的帶負載能力的問題,就是如何高效的利用這些Server的問題,基本上也可以理解為如何提高玩家請求的並發處理能力的問題。
CPU廠商在很久以前就在考慮這方面的問題了,CPU其實也可以看成個黑盒。看看他們用過的技術——流水線(pipeline)技術、多CPU/多核(multicore)技術,以及這些技術的衍生技術。我想了很久讓 Server Cluster 內部處理並行的方法、並且有了比較清晰的思路之後,才發現其實早就可以參照CPU廠商的方法。流水線的方法就是把一個指令處理拆分成很多個步驟,這樣指令的處理被分解之後就可以部分重疊(相當於變成並發的了)執行。我們的Server Cluster一樣可以用這種方法來拆分,我想了個名字——
Services-based Architecture——基於服務的架構。在這種架構內部,我們根據處理資料、邏輯的相關性來劃分組內各個伺服器的工作任務。例如:位置服務提供物體可見度資訊、物品服務處理所有物品相關的邏輯、社會關係服務提供行會家族等等方面的邏輯、戰鬥伺服器只處理戰鬥相關的邏輯,等等。這樣劃分的話、邏輯處理的並發就有了可能性。舉例來說:A砍B一刀這件事情與C從奸商手裡買到一件武器這個事情是完全不相干的,而且這2個請求本來就在不同的伺服器上被處理,他們是被不同的Service Server並發處理的。這就是 Services-based Architecture 的並發方法。
基本上,把遊戲邏輯的處理拆分成一個個的service,就和設計cpu的時候把機器指令的具體處理拆分,然後設計出一個個流水線單元是一個道理。
Cells-based Architecture——基於cell的架構。每個cell都在不同的物理server上面運行著完全一樣的應用程式伺服器,但是他們負責承載不同的遊戲情境地區的遊戲邏輯。和 services-based arch. 明顯不同的就是,每個cell都是個‘在邏輯上完整的’伺服器。它得處理物品操作、人物移動、戰鬥計算等等幾乎所有的遊戲邏輯。儘管這麼做會帶來一些(可能是很複雜)的問題,但是它完全是可行的。舉例來說:在吳國A砍B一刀顯然地和千裡之外在越國的C砍D一刀不搭界,他們完全可以被不同的Cell並發地處理。
基本上,這就相當於一個主板上面插多個CPU或者一個CPU但是有多個核心,每個CPU能做的事情都是一樣的,而且能一起做。
關於這兩種 seamless world 架構的基本分析和需要解決的一些主要問題,下次再寫。
上次寫了《無縫世界網遊伺服器架構的設計思路》,這次是續篇,主要內容是兩種架構的優缺點分析。
從一組伺服器的角度來看,一般來說,我們的伺服器組(Cluster)內都會有登陸驗證伺服器(Login Server)、持久性資料服務器(DB及DB Proxy)、串連Proxy 伺服器(Gate Server、FEP Server、Client Proxy等)以及Auto Patch Server、還有用於集中管理及控制組的伺服器等等,由於這些伺服器基本上什麼樣的架構設計都會用到,所以——現在不考慮以上這些伺服器,只考慮具體處理遊戲邏輯、遊戲規則的各個伺服器。以此為前提來分析一下 Services-based Architecture 和 Cells-based Architecture 的優缺點。
對Services-based Architecture 的分析
基於服務的架構,顧名思義這種架構的實現(程式)會是和服務的具體內容(策劃)相關的,這是因為——各種【服務】內容的確定是建立於項目的【需求分析】基礎上的,【需求分析】的前提是基本確定了【策劃設計】,至少是項目的概要設計。
我想多數做過遊戲項目的人都應該對需求變更有很深的感觸,每個人都說“開始想做的那個和最後實際做出來的那個不一樣”。特別是在項目的早期階段,團隊的不同成員對項目做完之後的樣子有相當不同的看法(很可能大家互相都不知道對方怎麼看的),這很容易理解,誰也不可能從幾頁紙幾張圖就確切地知道這個遊戲做完了什麼樣子,即使不考慮需求變更。涉及到項目開發方法方面的東西這裡就不多說了,總之我的看法就是——儘管我們不大可能設計出一個架構能夠適應任何的遊戲設計,但是不同開發工作單位間的耦合度顯然還是越低越好,基於服務的架構適應需求變更的能力較差。
關於服務耦合
不管如何劃分service,不同 service之間都一定存在不同程度的耦合(coupling)關係,不同的 service 之間會有相互依賴關係。而你們的策劃設計可能會讓這種關係複雜到程式在運行時的狀態很難以琢磨的程度。
假設:
伺服器組內的戰鬥處理和物品處理分別由兩個不同的服務(器)提供
遊戲規則:
人物被攻擊後自己攜帶的物品可能掉落到地上
某些物品掉落後會爆炸
物品在地上爆炸可能傷及周圍(半徑10米內)人物
人物之間的‘仇恨度’影響戰鬥數值計算
被攻擊時掉落的物品爆炸後傷及的人物,會增加對‘被攻擊人’的‘仇恨度’
我想我還能想出很多很多“看上去不算過分”的規則來讓這個事情變得複雜無比,很可能你們的策劃也在無意中,已經擁有我這種能力 而且他們在寫文檔時候的表達還多半不如我上面寫的清楚,另外,他們還會把這些規則分到很多不同的文檔裡面去寫。好吧,你肯定會想“把這兩個服務合二為一好了”,實際上不管你想把哪兩個(或多個)服務合并為一個服務的時候,都應該先考慮一下當時是為什麼把他們獨立為不同服務的?
實際上很多這樣“看上去不算過分”的規則都會導致service間的頻繁互動,所以每個service最好都是stateless service,這樣的話情況會好很多,但是對於遊戲來說這很難做到。
請求處理的時序問題
服務耦合的問題在不考慮開發複雜度比較高的情況下,還是可以被搞定的,只要腦袋夠清醒,願意花夠多的時間,那麼還有更難以搞定的嗎?我看確實還有,如果你對將要面對的問題,瞭解得足夠多的話:)
上面兩個順序圖表描述的是某個玩家做了連續做了兩次同樣的操作但是很可能得到了不同的結果,當然這些請求都是非同步地被處理。問題的關鍵在於——儘管兩次玩家執行的命令一樣、順序一樣,甚至時間間隔都一樣,但是結果卻很不同——因為圖(1)裡面C2CS::Request_to_attack請求被處理的時候,C2IS::Request_equip_item 這個請求還沒有被處理完,但是圖(2)顯示的情況就不一樣了。因為C2IS::Request_equip_item這個操作很可能會改變遊戲人物的屬性,這個屬性又很可能影響attack的結果。這兩幅圖實際上省略了 Combat Server 與 Item Server 之間的互動過程。但是已經足以說明問題了,每個Service處理每個Request時具體會消耗的時間,是無法在設計時確定的!
誰喜歡這類結果上的不確定性?舉個例子:玩家很可能已經裝備上了“只能使用1次的魔獸必殺刀”然後攻擊了一下魔獸,但是它卻沒死!這會導致什麼樣的結果?請自行想象。另外,這種不確定性還會表現為“在項目開發期和運營期的行為差異”,或者“出現某些偶然的奇怪現象”。
那還有解決方案嗎?有的,其實只要序列化玩家請求的處理,使處理有序進行就可以了。但是又一次的,這會帶來新的複雜度——在某個範圍(整個伺服器組?一個行會?一個隊伍?)內,以每個玩家為單位,序列化他(們)的(可能是所有)操作,但是也顯而易見,這在某種程度上降低了請求處理的並發性,儘管它對並發性的影響可能只局限於不大(最少是一個玩家)的範圍。
對Cells-based Architecture 的分析
基於Cell的架構有個明顯的優勢就是Cell如何劃分和你的策劃沒有關係J這是真的。而且Cell間如何互動可以被放到系統的底層,具體有多底層、多隱蔽(實際上可以隱蔽到對開發上層遊戲邏輯的程式員都不可見的程度)要看你的實現如何了。如果做到了某個系統的程式設計與遊戲設計完全無關的話,顯然,這個系統受到遊戲設計變更(需求變更)的影響就會很小很小,甚至會到完全不受影響的程度,當然這是理想情況。
關於跨邊界對象互動
在基於Cell的伺服器架構裡面,實現無縫世界(Seamless World)的主要痛點在於實現跨邊界對象的互動時會出現的一些問題,因為這些對象在不同的Cell進程裡面,這些Cell一般來說是在不同的物理伺服器上運行。
無縫世界的特點自然就是無縫,並且因為無縫給玩家帶來更好的遊戲體驗,所以顯然我們希望“跨邊界對象互動”問題不把事情搞砸,那麼這種互動的表現就必須滿足穩定、高效的前提。一般來說,高於300ms的延遲對玩家操作來說就屬於“明顯可見”的程度了,不能讓玩家騎著500塊RMB買來的虛擬馬在一片大草原上面暢快的奔跑的時候,在某個地方突然就被“看不見的牆”給“擋”了一下,因為這“牆”根本看不見,所以會很影響“上帝”的遊戲心情。
關於組成整個虛擬世界的Cell之間的關係,下面來分析兩種情況:
Cell 承載的情境不重疊
(1),一個連續的虛擬世界情境被分成左右兩塊,分別在不同的Cell Server上面運行。A、B、C分別是3個不同的遊戲角色。在這種情況下B與C的互動並不存在任何障礙,因為B和C只不過是同一個物理伺服器上同一個進程內的兩塊不同的記憶體資料而已。但是A與B/C的互動就不那麼直接了,儘管他們所在的情境看上去是“連續的、一體的”但是事情不會像表面上那麼簡單。A與B發生互動時候會發生什麼事情?例如A攻擊了B、A與B交易物品等等,因為在這種結構下做資料同步會帶來很多問題,例如對象狀態不確定性、開發複雜度等等、相對來說兩個Cell Server之間做網路通訊而帶來的延遲可能反而是最小的問題,這些問題不需要很複雜的分析就可以得出結論,在此不再多說了。
二,Cell 承載的情境(部分地)重疊
(2),一個連續的虛擬世界情境被分成左右兩塊,分別在不用的Cell Server上面運行。A、B、C、D分別是4個不同的遊戲角色。這個情況下,中間的地區為2個Cell所共同維護,中間地區的對象同屬於2個Cell所‘擁有’。這有什麼好處?現在,任意兩個對象之間,除了A與C之間的互動,都變得更‘直接’了。變得直接肯定是一件好事兒,那麼A與C之間呢?他們之間其實也沒有任何問題J 因為雙方都已經超出了對方的Area of Interest(AoI)地區,遊戲規則可以限制他們不能直接互動。
上面提到的第二種方案算不上什麼魔法,但是肯定是比第一種方案更有效。接下來怎麼辦?假設B是個玩家,他站在中間這塊地區上面時,並不會產生“我到底是在哪裡”這樣的疑問J 問題的關鍵在於對於Cell Server來說,怎麼樣同步那些處於重疊地區對象的狀態。遊戲世界內的對象可能同時處於1個、2個、3個或者4個不同的Cell Server。如果你的Cell分隔方法不限於水平線和垂直線、或者有人故意搗亂的話,還可能會更多。需要被同步的對象也不只是玩家本身,還包括怪物、NPC、一顆會走的樹、某玩家在地上吐的痰等等。
由於我們的基於無縫世界的遊戲規則不大會直接去限制遊戲世界某處玩家的行為,也就是說玩家如果能相互交易物品的話,他們肯定希望在任何地方都能交易,“為什麼其他地方都行,但是在某個牆角做交易就會導致物品丟失?”所以比較可靠的方法是建立一套的用於同步的底層機制,來同步這些跨邊界對象。
怎麼實現?這個話題很大,恐怕再寫幾篇Blog我也講不完,但是有一些東西可以作為參考,例如:DCOM和CORBA規範,Java的RMI,基於Python的 PYRO,TAO(The ACE ORB)等等。好在分散式處理的問題不止是網路遊戲會涉及到,可以借鑒的東西還是很多的。
總結
很顯然,這篇文章在兩種架構的評價上面存在某些傾向性,但是傾向性本身只是副產品。另外一個副產品就是關於一些技術分析方法。
在考慮採用何種技術的時候,我們往往很容易地就會忽略對程式之外那些事情的影響。上面我提到的關於Services-based架構實現的時候,提到劃分service及資料設計對程式設計能力的挑戰、對策劃設計的制約,對適應需求變更能力的影響,都不會只是空談。這些問題也不是只在實現這種架構的時候才出現。
不要高估自己的智商,Keep It Simple and Stupid 應該可以讓我們離成功更近一點兒。
原文地址:
http://canremember.com/?p=8
http://canremember.com/?p=10
http://dearymz.blog.163.com/blog/static/2056574200811119845551/