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

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"
local cluster = require "skynet.cluster"
require "skynet.manager"
 
skynet.start(function()
    cluster.reload({
        node1 = "127.0.0.1:7001",
        node2 = "127.0.0.1:7002"
    })
    local mynode = skynet.getenv("node")

    if mynode == "node1" then
        cluster.open("node1")
        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
        cluster.open("node2")
        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 cluster = require "skynet.cluster"
local mynode = skynet.getenv("node")

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")
        local pong = cluster.proxy("node2", "pong")
        skynet.send(pong, "lua", "ping", "node1", "ping1", 10)

主站蜘蛛池模板: 桑日县| 密云县| 铜梁县| 林口县| 东兴市| 阿图什市| 喀喇| 道真| 九龙坡区| 潢川县| 巧家县| 楚雄市| 田东县| 登封市| 广南县| 呼伦贝尔市| 南召县| 淮南市| 积石山| 贺州市| 苗栗市| 林州市| 米脂县| 太湖县| 安顺市| 鸡东县| 隆安县| 门头沟区| 德兴市| 灯塔市| 从化市| 旬阳县| 称多县| 长海县| 南平市| 湛江市| 民权县| 淄博市| 惠州市| 韶关市| 高唐县|