面向微服務的服務端架構
一年前的這個時候,小說君在知乎上提了個問題,關于pomelo框架與網易內部部分開源的服務端框架mobile_server。
當然,后來的發展也在預料之中,回答者寥寥。不少回答的同學要么是匿名噴,要么是灌了個水之后刪掉。一年過去,也并沒有哪位既用過mobile_server,又了解過pomelo的同學參與回答,非常遺憾。
考慮到有些同學并不是做游戲服務端的,一是不了解游戲服務端開發中的一些術語,二是可能不太了解前述兩個架構,所以這里就簡單提一下。
這兩個架構實際上主體部分都與之前的「面向中間件的開發模式」一文最后形成的架構大致相似,貼張圖:
當然,除了節點命名的區別之外,兩種架構還是與圖中有所出入。
比如除了Gate之外,仍然有一個獨立的前端服務器,供玩家登錄以及負責為玩家分配要建立長連接的Gate服務器。
另外簡單介紹下:
-
圖中的Gate是一種反向代理服務器,可以理解為web開發中的nginx。
-
圖中的中心進程和場景服務器都是應用服務器,可以理解為web開發中的tomcat節點。
-
圖中的DbProxy既有部分業務邏輯,又具有一定的緩存功能,不過完全不影響將之理解為web開發中的緩存服務器,比如redis。
mobile_server大體如此,而pomelo則是在這個基礎上引入了更多web開發的思路。還是舉一些例子:
-
看pomelo的官網,我們就能發現,里面用的route、filter、channel,app應用框架的插件化形式加載模塊,以及一些實現的較獨立的monitor、watchdog、admin-tool等,都帶著web開發的影子。
-
pomelo的配置比較統一,都是json。不像游戲服務端框架喜歡用ini或者xml。
-
對分布式的支持。大部分游戲服務端框架雖然號稱 「 分布式框架 」,其實對分布式的支持很有限,能支持個gate集群或者game集群已經算是相當不錯了。但是服務發現、守護進程這些都不會有。
-
pomelo還借助zookeeper幫一些全局單點做了高可用,當然細節我沒仔細看,可能首先還是要先把這些全局單點搞成無狀態服務才行。
說了這么多,想必各位可能覺得小說君是在安利pomelo。當然不是,pomelo有一個致命的硬傷,就是這是js寫的。且不說在大佬們眼中C#/JAVA寫服務端就已經非主流了,純js簡直死刑,單說團隊協作這塊,js這種弱類型語言對于中等規模的開發團隊簡直是噩夢。這也是pomelo在網易內部都一直不溫不火的原因。但是說來也怪,這些語言上的硬傷都并不能阻擋js成為程序員中的主流。
相比之下,mobile_server雖然是c++搭python,但是由于是bigworld這種老牌引擎改過來的,上層顧慮少,資源投入多,有成功產品用了大家就都會用。但是像一開始提到的那個知乎問題下面,有人答了又刪的答案所說mobile_server就是屌、pomelo就是渣這種論斷,就顯得成敗論英雄了。
扯了這么多八卦,下面我們還是言歸正傳,看看標題「面向微服務的服務端架構」。
十年前,有幾個縮寫比較火,一個是SOA(Service-Oriented Architecture,面向服務架構),一個是SaaS(Software as a Service,軟件即服務)。
兩個概念描述的其實是差不多的意思,那就是業務系統一定要服務化,才能有一個比較高的抽象層次,繼而做到服務級別的復用。
現在兩個概念已經沒什么人提及了,一方面是因為SOA和SaaS還是企業應用這塊吹的比較多的,另一方面是互聯網技術日新月異,框架層出不窮,用一個新框架三分鐘搭出一個業務,誰還關心服務如何復用?
但是,不可否認, 「服務化」一直都是一種比較現代化的解耦思路。因此SOA雖然已經淡出視野,但是后續的類似概念立刻跟上。
「微服務 」是這樣一種概念,邏輯被劃分為一個個的獨立單元,每個單元的邏輯非常簡單,高度內聚,單元之間是正交的。單元與單元是物理隔離的,相互之間借助異步機制通信。如此一來,每個單元就可以稱為一個 「微服務 」。
繼續拿出之前對比的pomelo與mobile_server,小說君之所以更欣賞pomelo,也是因為其在服務化上做的探索要比mobile_server多很多。
pomelo把游戲服務端中一些常見的邏輯集以 「 組件 」的形式呈現,一個pomelo節點可以選擇加載多種不同的 「組件 」。服務器 本身是鴨子類型的,也就是說,只要某個服務器 加載了某些組件,那么這個服務器 就能提供這些服務,屬于這些服務的一個實例。
但是這樣還不夠。
我們說微服務有一個特點,那就是 「物理隔離 」。如果僅僅是通過插件化來做服務加載,那是無法做到物理隔離的。
典型比如兩個微服務都依賴了同一個lib,這個lib中有一個單例,這樣兩個微服務就都能訪問到這個單例,違反了物理隔離的要求。
除此之外,還有一個致命問題,我們需要畫個架構圖才能看出問題所在。
假設我們有一套服務端架構,采用了 「 微服務 」 的設計理念。其中有兩種微服務分別是A和B,各有4和3個服務實例。
A需要訪問B提供的服務,B需要訪問A提供的服務。那么簡單的連接拓撲圖如下所示:
相互之間需要的連接數是n*m。
當然,這還是比較簡單的情況。在真實應用中,需要互調的微服務不可能只有A和B兩種,更多的情況是A、B、C、D四者,甚至更多微服務之間互調,假設每類服務的實例都有復數個,那連接拓撲簡直沒法看。
那么,面向微服務的服務端架構,如何才能解決上述兩個問題?
其實只要稍微換個思路,就能輕易解決。
首先看物理隔離。如果服務是一個基本單元,那我們如果想做到單元之間的物理隔離,需要用什么樣的容器?
最簡單的就是操作系統級別的,一個進程即是一個單元,而且是絕對物理隔離的。
這樣實現的好處就是足夠簡單——框架簡單,開發起來更簡單。
但是問題也有,想實現一套統一的服務間通信機制就只能依賴進程間通信(同物理機),或網絡(跨物理機)。
其次就是語言級別的,這方面也有比較成熟的方案,那就是Erlang的actor模型。一個actor就是一個單元,同樣是物理隔離。
而且Erlang/OTP還提供了消息通信機制,語言層面做到這些,自然可以在底層做更多的優化,比如同物理機的actor間通信完全可以zero copy。
還有一種是虛擬機級別的,云風的skynet就是如此,lua_State具有物理隔離特性,因此skynet的每個服務單元的容器就是一個lua_State。
再看連接拓撲的問題。這個問題想解決就更簡單了,假設所有的服務都與一個harbor建立連接,由harbor統一路由消息,拓撲就變成了下圖的樣子:
連接數量簡化為了n+m。
這個harbor其實就是一個消息隊列中間件,具體實現中,可以像skynet那樣用一個高度簡化的、數據結構層面上的消息隊列來做,也可以用一些第三方的成熟消息隊列中間件來做。
之后一篇服務端系列文章,小說君就會詳細介紹下如何基于RabbitMQ這個消息隊列中間件來實現這樣一種服務端架構。
微服務與服務劃分作為web開發中的一種比較現代化的理念,已經得到了充分的應用,但是游戲程序員往往對此不以為然。 文章最后,小說君想再聊聊對游戲服務端的意義。
游戲服務端實踐中,針對之前提到的連接拓撲復雜的問題,一種治標不治本的方法是抬高添加新進程的成本,比如如非必要不會允許你增加額外進程,但是這樣更深度的解耦合就成了幻想。
另一方面,游戲服務端的應用層與連接層難以分離。舉個例子,在文章開頭介紹的設計思路中,兩個進程有沒有連接是一種不確定態,設計的時候覺得沒有,結果某個需求來了,不建立連接就很難實現。這樣對于應用層來說,就需要提供連接的概念,某個進程必須先跟其他進程連接建立成功了,然后才能調用其他進程提供的服務。
而實際上,更優雅的設計是應用層完全不關注連接細節,只需要知道其他的進程提供了服務,自己就能獲取到這種服務。
當然,游戲服務端開發中,也是會出現服務劃分的需求的,最典型比如聊天服務。但是,公司內不同項目,往往直接用同一套聊天服務代碼庫,達到代碼級別的復用。這樣做的結果往往就是,每個團隊都會從更早的團隊拿過來聊天業務代碼,然后自己改造改造,成了完全不同的分支,最后連代碼復用都做不到了。
從另一個思路來講,同一款游戲的不同組服務器,其實也只需要同樣的一組聊天服務。但是如果按傳統的模式,一組服務器只能開零或一個聊天服務器,事實上,有可能某10組服務器用1個聊天服務器就夠了,而某1組服務器用1個聊天服務器壓力都有些大。
但是很顯然,如果做了服務劃分的改造,比如聊天服務直接采用一些第三方的IM云服務,這些問題就都煙消云散了。
來自:http://mp.weixin.qq.com/s?__biz=MzIwNDU2MTI4NQ==&mid=2247483728&idx=1&sn=c2076dbc98de6fbd40b87236f2033925&chksm=973f0fbaa04886ac83c975b7046885f7171be8d26695d23fcab974124ce054a65d10caea3db5#rd