官术网_书友最值得收藏!

1.4 用分布式擴能

對于中大型商業(yè)游戲來說,往往出現(xiàn)全服爆滿的現(xiàn)象(如圖1-12),1000多人的承載量遠遠不夠。根據(jù)游戲廠商的新聞稿可知,2012年《夢幻西游》最高同時在線玩家達到了270多萬人;2016年《王者榮耀》的同時在線玩家超過了300萬人。既然單個程序的承載量有限,最直接的辦法就是開啟多個程序來提高承載量。

圖1-12 玩家爆滿的游戲畫面示意圖

1.4.1 多個程序協(xié)同工作

圖1-13展示了一種由多個程序共同協(xié)作的服務端模型,圖中程序A和程序B分別處理客戶端消息,程序C作為中轉(zhuǎn)站,負責程序A和程序B之間的通信。每個程序均獨立運行,可以將其部署在不同的物理機上,形成天然的分布式系統(tǒng)。

圖1-13 多進程服務端示意圖

說明:為統(tǒng)一術(shù)語,本書中“服務端”代表整個游戲服務端系統(tǒng);“程序”“進程”或“節(jié)點”代表一個操作系統(tǒng)進程;“物理機”代表服務器,涵蓋了實體服務器和云服務器。

盡管單個程序還是最多承載1000余人,但是只須開啟1000個程序,并將其布置在數(shù)百臺物理機上,理論上就可以支撐100萬玩家,總承載量得以提高。

1.4.2 三個層次的交互

在分布式結(jié)構(gòu)中,數(shù)據(jù)的交互被分成了三個層次,如表1-2所示。這就要求開發(fā)者能對游戲業(yè)務功能做出合理的切分。在游戲中,有些功能是強交互的,有些功能是弱交互的。以MMORPG為例,同一個場景的角色交互很強,每走一步都要讓對方知道,可以在同一個程序中處理同一個場景邏輯;不同場景的角色交互較弱,只有聊天、好友、公會這些功能需要交互,可以將同一個服務器的玩家都放在同一臺物理機上處理;不同服務器的玩家交互很少,可以放到不同的物理機上。

表1-2 不同交互場景的區(qū)別

1.4.3 搭個簡單的分布式服務端

理論歸理論,實踐出真知。實現(xiàn)1.2.4節(jié)的“走路”程序是場景服務器的一項主要功能,盡管一個場景只能支撐數(shù)十人,只要多開幾個場景就能夠支持更多玩家。本節(jié)將實現(xiàn)圖1-14所示的分布式程序,系統(tǒng)中有兩個“走路”程序,分別代表獸人村落和森林兩個游戲場景,客戶端直接連接角色所在的場景,玩家只能看到所在場景的角色,不同場景角色可以全服聊天。該程序可分成三個步驟實現(xiàn)。

圖1-14 簡單的分布式系統(tǒng)

第一步,編寫聊天服務器。聊天服務器其實是轉(zhuǎn)發(fā)服務器,它管理著場景服務器發(fā)來的連接(見代碼1-5中的scenes),只要收到場景服務器的消息,它就會廣播給所有的場景服務器。聊天服務器會監(jiān)聽8010端口,等待場景服務器連接。

代碼1-5 聊天服務器(Node.js)

(資源:Chapter1/3_chat_server.js)


var net = require('net');

var scenes = new Map();

var server = net.createServer(function(socket){
    scenes.set(socket, true) //新連接

    socket.on('data', function(data) { //收到數(shù)據(jù)
        for (let s of scenes.keys()) {
            s.write(data);
        }
    });
});

server.listen(8010);

第二步,讓場景服務器(“走路”程序)連接聊天服務器。場景服務器即是服務端又是客戶端,對于玩家來說,它是服務端,對于聊天服務器來說,它又是客戶端。在“走路”程序的基礎(chǔ)上,讓場景服務器連接聊天服務器(見代碼1-6中的net.connect),當場景服務器收到聊天服務器發(fā)來的數(shù)據(jù)時,就會把它原封不動地廣播給客戶端。

代碼1-6 場景服務器的部分代碼,用于連接聊天服務器(Node.js)

(資源:Chapter1/3_walk_server.js)


var net = require('net');
//"走路"程序略 server.listen(8001);

var chatSocket = net.connect({port: 8010}, function() {});
chatSocket.on('data', function(data){
    for (let s of roles.keys()) {
        s.write(data);
    }
});

第三步,給場景服務器添加聊天功能(見代碼1-7)。假設(shè)客戶端除了發(fā)送“l(fā)eft”“right”等指令外,還會發(fā)送聊天文字,那么在收到聊天消息后它會把消息原樣發(fā)給聊天服務器。整個消息流程是:①場景服務器將聊天消息發(fā)送給聊天服務器;②聊天服務器把消息廣播給所有場景服務器;③各個場景服務器分別將聊天消息廣播給場景中的所有玩家。

代碼1-7 場景服務器處理聊天消息的部分代碼(Node.js)

(資源:Chapter1/3_walk_server.js)


    //接收到數(shù)據(jù)
    socket.on('data', function(data){
        ……
        //更新位置
        if(cmd == "left\r\n") role.x--;
        ……
        else { 
            chatSocket.write(data);
            return;
        };
        …… 
    });

現(xiàn)在可以進行測試了,先運行聊天服務器,再依次運行兩個場景服務器(假設(shè)監(jiān)聽的端口分別為8001和8002)。如圖1-15所示,客戶端A和B連接第一個場景服務器,客戶端C連接第二個場景服務器,服務器中的小方塊代表各個程序,方塊中的數(shù)字代表該程序的監(jiān)聽端口。當客戶端A走動時,因為A、B同在一個場景中,所以它們會收到移動消息,而客戶端C不在同一場景中,因此它不會收到;若客戶端A發(fā)送聊天信息“戰(zhàn)神公會招人”,三個客戶端都能收到。

圖1-15 測試分布式服務端

1.4.4 一致性問題

分布式程序要處理很多異常情況。如果程序部署在不同物理機上,連接不太穩(wěn)定,需要處理好斷線重連、斷線期間的消息重發(fā),以及斷線后進程間狀態(tài)不一致的問題。圖1-16展示的是因網(wǎng)絡(luò)不暢通導致的異常情形,假如客戶端A的玩家向客戶端B的玩家購買道具,消息需要通過程序C中轉(zhuǎn),因程序A和程序C之間的網(wǎng)絡(luò)連接出現(xiàn)異常,出現(xiàn)了客戶端B的玩家被扣除了道具,客戶端A的玩家卻沒得到道具的情況。程序A與程序C的網(wǎng)絡(luò)連接異常,游戲功能受到了影響,就算一段時間后重新連接上,兩個進程的狀態(tài)也可能會不一致。

圖1-16 分布式程序的異常情形

一致性問題是分布式系統(tǒng)的一大難題,在游戲業(yè)務中,開發(fā)者一般會把一致性問題拋給具體業(yè)務去處理。對于圖1-16所示的異常情況,需要給每個交易賦予唯一編號。程序C除了轉(zhuǎn)發(fā)消息,還需要記錄程序A對每個交易的執(zhí)行狀態(tài),如果轉(zhuǎn)發(fā)失敗,程序C要在稍后重發(fā)交易消息,直到程序A成功執(zhí)行。而程序A也需要記錄每個交易的狀態(tài),如果某個交易已經(jīng)成功執(zhí)行,則不再響應程序C發(fā)來的消息,避免重復添加道具。

另外,管理數(shù)百臺物理機、成百上千個程序也不容易,第一,物理機多了,某一臺出故障的可能性很大;第二,開啟或關(guān)閉全部程序要花費很長時間。

主站蜘蛛池模板: 泰安市| 永清县| 陈巴尔虎旗| 哈巴河县| 五寨县| 兴义市| 金沙县| 宁都县| 抚远县| 台东市| 大渡口区| 河南省| 南汇区| 吉木萨尔县| 宜君县| 云梦县| 乐清市| 平塘县| 额尔古纳市| 灵武市| 冕宁县| 辽宁省| 华宁县| 肃宁县| 焦作市| 莫力| 饶河县| 重庆市| 华蓥市| 沙河市| 汨罗市| 若尔盖县| 额敏县| 静安区| 宁城县| 徐水县| 都兰县| 大方县| 青州市| 安义县| 黑山县|