- 百萬在線:大型游戲服務端開發
- 羅培羽
- 1483字
- 2021-09-17 17:04:57
2.8 使用節點集群建立分布式系統
一臺物理機的承載量有限,現代服務端都采用分布式服務模式。Skynet提供了cluster集群模式,可讓不同節點中的服務相互通信。
2.8.1 功能需求
此處的需求是將2.7節的ping程序改成分布式。如圖2-32所示,先在節點2開啟兩個ping服務(ping1和ping2),然后再開啟另一個ping服務(ping3),讓ping1和ping2分別向ping3發送消息,ping3給予回應,如此往復。

圖2-32 分布式ping
2.8.2 學習集群模塊
圖2-33展示了Skynet的cluster集群模式。在該模式中,用戶需為每個節點配置cluster監聽端口(即圖中的7001和7002),Skynet會自動開啟gate、clusterd等多個服務,用于處理節點間通信功能。假如圖2-33的ping1要發送消息給另一個節點的ping3,流程是節點1先和節點2建立TCP連接,消息經由Skynet傳送至節點2的clusterd服務,再由clusterd轉發給節點內的ping3。

圖2-33 cluster集群示意圖
skynet.cluster模塊提供節點間通信的API如表2-8所示。
表2-8 cluster集群的API

更多API參見https://github.com/cloudwu/skynet/wiki/Cluster。從圖2-33也可看出,節點間通信有著較大的代價,不僅消息傳遞速度慢,安全性也得不到保障(如某個節點突然掛掉)。從Skynet的特點來看,如果CPU運算能力不足,選用更多核心的機器遠比增加物理機性價比高。切記,任何企圖抹平服務運行位置差異的設計都需要慎重考慮。
2.8.3 節點配置
本節程序會開啟兩個節點,意味著需要用到兩份節點配置文件。復制兩份配置模板(2.2.2節),分別命名為Pconfig.c1和Pconfig.c2,將主服務改為“Pmain”,再添加node這一項,指定節點名稱。
examples/Pconfig.c1中新增的內容如下:
node= "node1"
examples/Pconfig.c2中新增的內容如下:
node= "node2"
2.8.4 代碼實現
1.主服務
每個節點都是從主服務開始運行的,主服務負責節點初始化并開啟其他服務。對照圖2-32來看,節點1開啟了兩個ping服務,節點2開啟了另外一個ping服務。
代碼2-12展示了主服務的代碼寫法,在執行cluster.reload之后,主服務先判斷當前的節點名稱(mynode,skynet.getenv表示從節點配置中讀取項目),如果是節點1則進入“mynode=="node1"”的分支,否則進入另一分支。每個節點都會調用cluster.open開啟集群監聽。
如果是節點1,主服務會開啟ping1和ping2這兩個服務,然后通過skynet.send發送start指令。由于是分布式程序,因此相比于2.3節的程序,傳遞的參數要增加一個,代表讓ping1和ping2向node2節點的pong服務發送消息。如果是節點2,則開啟一個ping服務,并用skynet.name把它命名為“pong”。
代碼2-12 examples/Pmain.lua
(資源:Chapter2/6_cluster_main.lua)
local skynet = require "skynet"skynet.start(function() local mynode = skynet.getenv("node") if mynode == "node1" then local ping1 = skynet.newservice("ping") local ping2 = skynet.newservice("ping") skynet.send(ping1, "lua", "start", "node2", "pong") skynet.send(ping2, "lua", "start", "node2", "pong") elseif mynode == "node2" then local ping3 = skynet.newservice("ping") skynet.name("pong", ping3) end end)
2.ping服務
集群的ping服務與2.3節的ping服務相似,程序結構如代碼2-13所示。變量mynode保存節點名稱(如“node1”)。
代碼2-13 examples/ping.lua的程序結構
(資源:Chapter2/6_cluster_ping.lua)
local skynet = require "skynet"local CMD = {} skynet.start(function() ...... 略 end)
ping服務包含ping和start這兩個消息處理方法,如代碼2-14所示。
在start方法中,參數source代表消息源,target_node和target分別代表目標服務的節點和地址,由主服務傳入。然后通過cluster.send向target_node節點的target服務發送名為ping的消息,這里帶有3個參數,其中mynode和skynet.self()代表自己所在的節點和地址,“1”是一個計數值。
在ping方法中,參數source_node、source_srv和count分別對應start方法的3個參數,前兩個參數代表消息發送方的節點、地址,最后一個參數count代表計數值。最后,通過cluster.send給發送方回應消息,并把計數值加1。
代碼2-14 examples/ping.lua中的部分內容
function CMD.ping(source, source_node, source_srv, count) local id = skynet.self() skynet.error("["..id.."] recv ping count="..count) skynet.sleep(100) clus ter.send(source_node, source_srv, "ping", mynode, skynet. self(), count+1) end function CMD.start(source, target_node, target) cluster.send(target_node, target, "ping", mynode, skynet.self(), 1) end
2.8.5 運行結果
先開啟節點2,再開啟節點1。節點2的運行結果如圖2-34所示,它會打印出“recv ping count=xxx”,由于節點1的兩個ping服務都會向節點2發送消息,因此同一計數值會出現兩次。節點1的運行結果如圖2-35所示,兩個服務分別收到節點2的回應。

圖2-34 節點2的運行結果

圖2-35 節點1的運行結果
2.8.6 使用代理
代碼2-15展示的是代理的使用方法,先將節點2的pong服務作為代理(變量pong),之后便可以將它視為本地服務,在此方法中通過skynet.send或skynet.call發送消息。
代碼2-15 examples/Pmain.lua中的重要內容
if mynode == "node1" then cluster.open("node1") local ping1 = skynet.newservice("ping") local ping2 = skynet.newservice("ping")
- UI設計基礎培訓教程
- 看透JavaScript:原理、方法與實踐
- Responsive Web Design with HTML5 and CSS3
- Web Application Development with MEAN
- Spring實戰(第5版)
- PLC編程及應用實戰
- Rust Essentials(Second Edition)
- 從零開始學C語言
- Python機器學習之金融風險管理
- Application Development with Swift
- Implementing Microsoft Dynamics NAV(Third Edition)
- Keil Cx51 V7.0單片機高級語言編程與μVision2應用實踐
- Java程序設計
- Python面向對象編程(第4版)
- 打造流暢的Android App