◆邵 嵐 隋玉亮 李 真
內(nèi)存房間管理在多人游戲中的研究與設(shè)計(jì)
◆邵 嵐 隋玉亮 李 真
(CLO 北京 100000)
多人玩法允許多個(gè)人在同一個(gè)房間中互動(dòng)游戲。本文主要描述了內(nèi)存房間的業(yè)務(wù)邏輯和功能,多個(gè)房間在內(nèi)存中的實(shí)現(xiàn)方式和效率優(yōu)勢(shì),研究了內(nèi)存房間列表、超時(shí)事件列表和異步任務(wù)隊(duì)列的原理及設(shè)計(jì)實(shí)現(xiàn)。
內(nèi)存管理;多人游戲;超時(shí)處理
目前,我公司開發(fā)的游戲玩法均以單人玩法為主,無論是目前線上的游戲,還是后續(xù)開發(fā)的新游戲,盡管在游戲題材、游戲類型、游戲風(fēng)格、動(dòng)畫效果(3D)、屏幕顯示(雙屏)等方面進(jìn)行了諸多創(chuàng)新,但是在游戲模式上,仍然都是單人參與的游戲,玩家之間的操作獨(dú)立互不影響,無法看到彼此的游戲狀態(tài),無法分享中獎(jiǎng)的喜悅,無法體驗(yàn)和朋友共同游戲的樂趣。
如今的游戲行業(yè),從早期的棋牌室、QQ游戲大廳[1],到紅極一時(shí)的MUD網(wǎng)頁游戲,再到現(xiàn)在蓬勃發(fā)展的各類手機(jī)游戲,則是多人玩法的天下。這些游戲玩法中,除了需要完成本身游戲邏輯的實(shí)現(xiàn),還額外需要解決多人游戲玩法特有的問題,如系統(tǒng)架構(gòu)的設(shè)計(jì)[2]、虛擬游戲房間的分配與管理、內(nèi)存及數(shù)據(jù)庫協(xié)同、游戲數(shù)據(jù)一致性與實(shí)時(shí)性、大并發(fā)量情況下如何解決系統(tǒng)的性能瓶頸[3]等。諸如此類的技術(shù)難點(diǎn),需要在多人玩法中逐個(gè)實(shí)現(xiàn)。
本文研究并實(shí)現(xiàn)了一套支持多人游戲玩法的架構(gòu)設(shè)計(jì),首次使用內(nèi)存(而非傳統(tǒng)的單純依賴數(shù)據(jù)庫)對(duì)游戲數(shù)據(jù)進(jìn)行管理,通過映射、雙向鏈表、異步隊(duì)列等技術(shù)手段實(shí)現(xiàn)對(duì)虛擬游戲房間的分配、管理、數(shù)據(jù)同步、事件響應(yīng)、回收、轉(zhuǎn)發(fā)等功能。本文的研究成果填補(bǔ)了我公司缺乏多人游戲玩法的空白,為今后的多人游戲策劃開發(fā)提供了技術(shù)借鑒與支持。
該項(xiàng)目系統(tǒng)由客戶端、游戲服務(wù)器、通知服務(wù)器、大廳服務(wù)器、數(shù)據(jù)庫等組成,是在原有的單人玩法系統(tǒng)中增加多人玩法,本文研發(fā)實(shí)現(xiàn)的房間管理功能位于游戲服務(wù)中,各模塊間的關(guān)系如圖1所示。
圖1 項(xiàng)目整體架構(gòu)圖
游戲終端和游戲服務(wù)器原有功能及連接不變,增加大廳服務(wù)器和通知服務(wù)器。游戲服務(wù)器使用FastCGI向通知服務(wù)器發(fā)送消息,通知服務(wù)器通過Corba把消息推送給大廳服務(wù)器,大廳服務(wù)器與游戲終端之間使用tsl加密的WebSocket互相通信。游戲服務(wù)器之間新增了轉(zhuǎn)發(fā)連接。
完成游戲的GUI展現(xiàn)和處理邏輯,負(fù)責(zé)調(diào)用游戲服務(wù)器的通信接口,根據(jù)收到的報(bào)文類型,把部分(或全部)報(bào)文轉(zhuǎn)發(fā)給大廳服務(wù)器,并接收大廳服務(wù)的廣播信息。
游戲服務(wù)器負(fù)責(zé)完成游戲邏輯控制,保存游戲數(shù)據(jù)。在每個(gè)游戲服務(wù)器上新增了內(nèi)存房間管理模塊,主要負(fù)責(zé)以下功能:虛擬房間的創(chuàng)建、管理、刪除;房間數(shù)據(jù)的同步與更新;對(duì)房間內(nèi)各玩家進(jìn)行監(jiān)控,處理超時(shí)事件;將不屬于本服務(wù)負(fù)責(zé)的游戲請(qǐng)求轉(zhuǎn)發(fā)給其他游戲服務(wù);服務(wù)異常之后的房間接管等。
在原有系統(tǒng)中增加通知服務(wù)器。通知服務(wù)器負(fù)責(zé)接收各個(gè)大廳服務(wù)器的注冊(cè)消息,以及各個(gè)游戲服務(wù)器發(fā)來的通知請(qǐng)求,并將消息推送給指定的大廳服務(wù)器。經(jīng)過重新設(shè)計(jì)后,通知服務(wù)器不再負(fù)責(zé)任何游戲邏輯及處理超時(shí)事件等,僅負(fù)責(zé)消息的推送,進(jìn)一步和游戲服務(wù)器解耦,使得模塊定位更加清晰準(zhǔn)確。
在原有系統(tǒng)中增加大廳服務(wù)器。大廳服務(wù)器接收各游戲客戶端的注冊(cè),并向通知服務(wù)器注冊(cè),收到通知服務(wù)器的通知信息后,解析消息內(nèi)容中目的地址字段,將通知信息通知到對(duì)應(yīng)的游戲客戶端。同時(shí)大廳服務(wù)器接收注冊(cè)過的客戶端發(fā)來的轉(zhuǎn)發(fā)消息,解析消息內(nèi)容中目的地址字段,將消息廣播給對(duì)應(yīng)的游戲客戶端。大廳服務(wù)器只負(fù)責(zé)信息轉(zhuǎn)發(fā),不處理任何游戲邏輯。
為多人游戲提供數(shù)據(jù)支持,包括玩家信息,游戲狀態(tài),投注數(shù)據(jù),房間信息,組隊(duì)信息等。
在多人玩法中玩家進(jìn)入游戲房間后有兩種游戲場(chǎng)景:一種是房間內(nèi)各玩家的投注互不干預(yù)但是可以互相觀看游戲結(jié)果,另一種是房間內(nèi)所有玩家處在同一畫面相互配合贏得游戲獎(jiǎng)勵(lì)。如果玩家在游戲房間中長(zhǎng)時(shí)間不操作,游戲服務(wù)器會(huì)根據(jù)不同游戲場(chǎng)景將玩家踢出房間或者自動(dòng)幫玩家完成游戲。同一房間內(nèi)任何一個(gè)玩家游戲狀態(tài)或者房間狀態(tài)發(fā)生變化時(shí)都會(huì)產(chǎn)生通知信息,并由系統(tǒng)推送給房間內(nèi)每個(gè)玩家,實(shí)現(xiàn)游戲畫面信息同步展示。
與房間管理相關(guān)的玩家業(yè)務(wù)操作主要有:玩家投注,加入房間,退出房間,超時(shí)。如圖2所示。
玩家發(fā)起投注請(qǐng)求時(shí),會(huì)在房間列表中找到對(duì)應(yīng)的玩家房間,以原子性的方式完成本玩家的一次投注過程,更新房間內(nèi)玩家的投注信息。
玩家進(jìn)入房間時(shí),系統(tǒng)自動(dòng)將該玩家匹配到當(dāng)前人數(shù)最多且未滿員的游戲房間,并在此游戲房間數(shù)據(jù)中增加本玩家信息和超時(shí)事件,如果沒有滿足條件的房間,則為玩家新建一個(gè)房間并加入房間列表,同時(shí)在超時(shí)列表中加入一個(gè)針對(duì)本房間的超時(shí)計(jì)時(shí)器。
圖2 房間業(yè)務(wù)處理圖
玩家退出房間時(shí),會(huì)在房間列表中找到對(duì)應(yīng)的玩家房間,將玩家信息從房間里刪除。如果玩家退出后,房間內(nèi)還有其他玩家,則在房間列表中保留本房間;如果玩家退出后,房間內(nèi)沒有玩家了,則將本房間從房間列表中刪除,同時(shí)從超時(shí)列表中刪除本房間計(jì)時(shí)器。
在房間中的玩家,如果長(zhǎng)時(shí)間不操作,會(huì)觸發(fā)超時(shí)事件。系統(tǒng)每隔1秒鐘會(huì)輪詢一次超時(shí)列表,發(fā)現(xiàn)對(duì)應(yīng)房間的計(jì)時(shí)器超時(shí),則將房間中的超時(shí)事件加入任務(wù)隊(duì)列執(zhí)行。觸發(fā)超時(shí)事件后,根據(jù)游戲場(chǎng)景不同將玩家踢出房間或者幫玩家完成游戲。
在以上業(yè)務(wù)操作中,如果玩家信息或者房間狀態(tài)發(fā)生變化需要同步到房間內(nèi)的所有玩家,則會(huì)生成同步信息,由推送系統(tǒng)推送到房間內(nèi)各玩家客戶端。
內(nèi)存房間管理將房間數(shù)據(jù)保存在內(nèi)存中,主要設(shè)計(jì)并實(shí)現(xiàn)了以下內(nèi)存數(shù)據(jù)結(jié)構(gòu)及管理機(jī)制:房間列表,超時(shí)列表,異步任務(wù)隊(duì)列,房間數(shù)據(jù)轉(zhuǎn)發(fā)與異?;謴?fù)。
為了快速的查找及增刪元素,房間列表采用map結(jié)構(gòu),對(duì)于每種游戲玩法,以房間編號(hào)作為key,以房間對(duì)象作為value形成map;為了使系統(tǒng)支持同時(shí)管理多個(gè)種類的游戲,本文進(jìn)一步以游戲編號(hào)作為key,以上述map結(jié)構(gòu)作為value,組成了新的map結(jié)構(gòu),即一個(gè)兩層結(jié)構(gòu)的map嵌套,如圖3所示。
圖3 房間雙層map圖
另外,本文設(shè)計(jì)了三個(gè)房間相關(guān)的互斥鎖,分別為map鎖、房間數(shù)據(jù)鎖、引用計(jì)數(shù)鎖。map鎖在增刪房間、查找房間以及獲取房間對(duì)象個(gè)數(shù)時(shí)使用,完成相應(yīng)操作后,先釋放map鎖,再返回得到的房間對(duì)象。在后續(xù)對(duì)房間對(duì)象進(jìn)行業(yè)務(wù)操作時(shí),調(diào)用房間數(shù)據(jù)鎖,保證房間數(shù)據(jù)的一致性,因?yàn)橐呀?jīng)釋放了map鎖,因此不會(huì)阻塞其他線程的查找等操作。引用計(jì)數(shù)鎖使用的場(chǎng)景為:當(dāng)成功創(chuàng)建或者找到房間時(shí)引用計(jì)數(shù)加1,當(dāng)清除數(shù)據(jù)資源時(shí)引用計(jì)數(shù)減一。總體來說,通過在不同場(chǎng)景調(diào)用三個(gè)功能不同的互斥鎖,可以在保持?jǐn)?shù)據(jù)一致性的前提下,最大限度的提高訪問效率,減少等待時(shí)間。
假設(shè)一個(gè)房間有n個(gè)玩家,每個(gè)玩家都會(huì)存在超時(shí)情況,房間本身也存在超時(shí)情況。這樣,一個(gè)房間中就會(huì)存在n+1個(gè)超時(shí)事件。如果每個(gè)超時(shí)事件對(duì)應(yīng)一個(gè)超時(shí)計(jì)時(shí)器,一個(gè)房間需要n+1個(gè)超時(shí)計(jì)時(shí)器,過多的超時(shí)計(jì)時(shí)器會(huì)增加管理難度并引起業(yè)務(wù)處理的混亂。
為了解決以上問題,采用兩級(jí)列表的方式處理超時(shí),第一級(jí)列表是每個(gè)房間的所有超時(shí)事件以雙向鏈表的方式按照超時(shí)時(shí)間排序掛入本房間數(shù)據(jù)結(jié)構(gòu)下;第二級(jí)列表是只將房間超時(shí)鏈表最近一個(gè)超時(shí)事件放入全局超時(shí)列表中,從而保證了每個(gè)房間在全局超時(shí)列表中只存在一個(gè)超時(shí)計(jì)時(shí)器,如圖4所示。
圖4 超時(shí)鏈表設(shè)計(jì)圖
系統(tǒng)每秒鐘輪詢一次全局超時(shí)鏈表,將超時(shí)事件放入異步任務(wù)隊(duì)列;當(dāng)異步任務(wù)隊(duì)列的超時(shí)事件執(zhí)行后,會(huì)從房間超時(shí)鏈表中取出下一個(gè)本房間的超時(shí)事件,放入全局超時(shí)鏈表。
在多人游戲業(yè)務(wù)中,即存在實(shí)時(shí)任務(wù),如投注請(qǐng)求,也存在異步任務(wù),如超時(shí)事件,通知事件。這些任務(wù)都是針對(duì)一個(gè)具體的房間執(zhí)行的,為了保證內(nèi)存房間數(shù)據(jù)的一致性,保證實(shí)時(shí)任務(wù)和異步任務(wù)的正常執(zhí)行,防止異步任務(wù)的阻塞與丟失,引入了異步任務(wù)隊(duì)列。所有的異步任務(wù)全部放入異步任務(wù)隊(duì)列,只有在當(dāng)前任務(wù)執(zhí)行完成后,才會(huì)從異步任務(wù)隊(duì)列取得下一個(gè)任務(wù)執(zhí)行,既保證了實(shí)時(shí)任務(wù)的及時(shí)處理,也保證了異步任務(wù)的處理,完整的實(shí)現(xiàn)了多人游戲的業(yè)務(wù)邏輯。如圖5所示。
圖5 實(shí)時(shí)與異步任務(wù)執(zhí)行圖
實(shí)時(shí)任務(wù)或異步任務(wù)執(zhí)行時(shí),都會(huì)從房間列表查找對(duì)應(yīng)房間,并鎖定房間數(shù)據(jù),執(zhí)行完成后才允許其他任務(wù)操作本房間數(shù)據(jù)。引入異步任務(wù)隊(duì)列后,把異步任務(wù)緩存到隊(duì)列中,只允許本房間的一個(gè)異步任務(wù)對(duì)房間數(shù)據(jù)加鎖,不會(huì)引起過多的任務(wù)同時(shí)對(duì)同一房間加鎖而阻塞,同時(shí)還能保證當(dāng)前任務(wù)執(zhí)行完后,就會(huì)執(zhí)行實(shí)時(shí)任務(wù)。異步任務(wù)隊(duì)列通過sem信號(hào)監(jiān)聽當(dāng)前隊(duì)列中是否有需要執(zhí)行的任務(wù),如果有則執(zhí)行,沒有則不做操作。
在現(xiàn)有系統(tǒng)架構(gòu)下,每個(gè)游戲客戶端可能連接到不同的游戲服務(wù),而這些客戶端因?yàn)閰⑴c多人玩法又被分配到同一個(gè)游戲房間,這會(huì)導(dǎo)致部署在不同游戲服務(wù)的內(nèi)存房間管理模塊互相爭(zhēng)搶游戲房間的控制權(quán),造成數(shù)據(jù)沖突與錯(cuò)誤。為了解決這一問題,本文新增了游戲服務(wù)之間的轉(zhuǎn)發(fā)連接,如圖1所示。該轉(zhuǎn)發(fā)消息通過iona中間件實(shí)現(xiàn),收到客戶端的請(qǐng)求后,先查詢數(shù)據(jù)庫,判斷該客戶端歸屬的房間服務(wù)url是否和本機(jī)的相同,如果相同則繼續(xù)處理業(yè)務(wù)請(qǐng)求,如果不相同,則需要向目標(biāo)url發(fā)送轉(zhuǎn)發(fā)的消息,交由目標(biāo)服務(wù)接管。
如果在上述轉(zhuǎn)發(fā)過程中發(fā)現(xiàn)目標(biāo)url的連接不存在,即表明目標(biāo)服務(wù)出現(xiàn)了異常,則需要本服務(wù)接管起目標(biāo)服務(wù)負(fù)責(zé)的所有房間,從數(shù)據(jù)庫讀取數(shù)據(jù)并恢復(fù)。待出異常的服務(wù)重新上線后,其會(huì)嘗試從數(shù)據(jù)庫恢復(fù)房間數(shù)據(jù),繼續(xù)提供服務(wù)。通過異常恢復(fù)與房間服務(wù)互相接管的設(shè)計(jì),保證了即使有部分房間服務(wù)發(fā)生異常,剩余的房間服務(wù)仍可以保持服務(wù)不中斷。
本文在公司現(xiàn)有單人玩法系統(tǒng)的基礎(chǔ)上,新增了通知服務(wù)器和大廳服務(wù)器,擴(kuò)展成可以支持多人游戲玩法的系統(tǒng),首次嘗試用內(nèi)存房間管理游戲數(shù)據(jù),通過雙層map映射、雙向鏈表、任務(wù)隊(duì)列等技術(shù)完成開發(fā),完整的實(shí)現(xiàn)了多人游戲的業(yè)務(wù)邏輯,既保證了數(shù)據(jù)一致性,又減少了對(duì)數(shù)據(jù)庫的頻繁讀取,提高了系統(tǒng)效率。通過本次多人游戲系統(tǒng)的研發(fā),不僅增加了公司技術(shù)儲(chǔ)備,同時(shí)也拓寬了游戲設(shè)計(jì)的角度與方式。
由于是首次進(jìn)行多人游戲系統(tǒng)的設(shè)計(jì),本項(xiàng)目在開發(fā)過程中不可避免的存在若干不足,如解決在消息推送過程中偶發(fā)的丟包現(xiàn)象;在長(zhǎng)時(shí)間、大并發(fā)的情況下系統(tǒng)的響應(yīng)時(shí)效與穩(wěn)定性;如何監(jiān)控各服務(wù)的狀態(tài)及發(fā)生異常后的處理等,均需在今后的研發(fā)過程進(jìn)一步的完善,并不斷探索新技術(shù)、新方式,以滿足未來游戲的需求。
[1]QQ游戲大廳體驗(yàn)報(bào)告.https://www.jianshu.com/p/df5 b66077949.
[2]從QQ游戲分布式架構(gòu)設(shè)計(jì)看“分而治之”等原則.http://tech.it168.com/a2009/1019/762/000000762733.shtml.
[3]QQ游戲百萬人同時(shí)在線服務(wù)器架構(gòu)實(shí)現(xiàn).https://blog.csdn.net/akunshouyoudou/article/details/56682869.