Cluster 集群
skynet 支持兩種集群模式。
如果你僅僅是單臺(tái)物理機(jī)的計(jì)算能力不足,那么最優(yōu)的策略是選用更多核心的機(jī)器,在同一進(jìn)程內(nèi),skynet 可以保持最高的并行能力,充分利用物理機(jī)的多核心,遠(yuǎn)比增加物理機(jī)性價(jià)比高得多
master/slave 模式
當(dāng)單臺(tái)機(jī)器的處理能力達(dá)到極限后,可以考慮通過內(nèi)置的 master/slave 機(jī)制來(lái)擴(kuò)展。具體的配置方法見?Config?。
每個(gè) skynet 進(jìn)程都是一個(gè) slave 節(jié)點(diǎn)。但其中一個(gè) slave 節(jié)點(diǎn)可以通過配置 standalone 來(lái)多啟動(dòng)一個(gè) cmaster 服務(wù),用來(lái)協(xié)調(diào) slave 組網(wǎng)。對(duì)于每個(gè) slave 節(jié)點(diǎn),都內(nèi)置一個(gè) harbor 服務(wù)用于和其它 slave 節(jié)點(diǎn)通訊。
每個(gè) skynet 服務(wù)都有一個(gè)全網(wǎng)唯一的地址,這個(gè)地址是一個(gè) 32bit 數(shù)字,其高 8bit 標(biāo)識(shí)著它所屬 slave 的號(hào)碼。即 harbor id 。在 master/slave 網(wǎng)絡(luò)中,id 為 0 是保留的。所以最多可以有 255 個(gè) slave 節(jié)點(diǎn)。
在 master/slave 模式中,節(jié)點(diǎn)內(nèi)的消息通訊和節(jié)點(diǎn)間的通訊是透明的。skynet 核心會(huì)根據(jù)目的地址的 harbor id 來(lái)決定是直接投遞消息,還是把消息轉(zhuǎn)發(fā)給 harbor 服務(wù)。但是,兩種方式的成本大為不同(可靠性也有所區(qū)別),在設(shè)計(jì)你的系統(tǒng)構(gòu)架時(shí),應(yīng)充分考慮兩者的性能差異,不應(yīng)視為相同的行為。
這種模式的缺點(diǎn)也非常明顯:它被設(shè)計(jì)為對(duì)單臺(tái)物理機(jī)計(jì)算能力不足情況下的補(bǔ)充。所以忽略了系統(tǒng)一部分故障的處理機(jī)制,而把整個(gè)網(wǎng)絡(luò)視為一體。即,整個(gè)網(wǎng)絡(luò)中任意一個(gè)節(jié)點(diǎn)都必須正常工作,節(jié)點(diǎn)間的聯(lián)系也不可斷開。這就好比你一臺(tái)物理機(jī)上如果插了多塊 CPU ,任意一個(gè)損壞都會(huì)導(dǎo)致整臺(tái)機(jī)器不能正常工作一樣。
所以,不要把這個(gè)模式用于跨機(jī)房的組網(wǎng)。所有 slave 節(jié)點(diǎn)都應(yīng)該在同一局域網(wǎng)內(nèi)(最好在同一交換機(jī)下)。不應(yīng)該把系統(tǒng)設(shè)計(jì)成可以任意上線或下線 slave 的模式。
slave 的組網(wǎng)機(jī)制也限制了這一點(diǎn)。如果一個(gè) slave 意外退出網(wǎng)絡(luò),這個(gè) harbor id 就被廢棄,不可再使用。這樣是為了防止網(wǎng)絡(luò)中其它服務(wù)還持有這個(gè)斷開的 slave 上的服務(wù)地址;而一個(gè)新的進(jìn)程以相同的 harbor id 接入時(shí),是無(wú)法保證舊地址和新地址不重復(fù)的。
如果你非要用 master/slave 模式來(lái)實(shí)現(xiàn)有一定彈性的集群。skynet 還是提供了非常有限的支持:
local harbor = require "skynet.harbor"-
harbor.link(id)?用來(lái)監(jiān)控一個(gè) slave 是否斷開。如果 harbor id 對(duì)應(yīng)的 slave 正常,這個(gè) api 將阻塞。當(dāng) slave 斷開時(shí),會(huì)立刻返回。
-
harbor.linkmaster()?用來(lái)在 slave 上監(jiān)控和 master 的連接是否正常。這個(gè) api 多用于異常時(shí)的安全退出(因?yàn)楫?dāng) slave 和 master 斷開后,沒有手段可以恢復(fù))。
-
harbor.connect(id)?和 harbor.link 相反。如果 harbor id 對(duì)應(yīng)的 slave 沒有連接,這個(gè) api 將阻塞,一直到它連上來(lái)才返回。
-
harbor.queryname(name)?可以用來(lái)查詢?nèi)置只虮镜孛謱?duì)應(yīng)的服務(wù)地址。它是一個(gè)阻塞調(diào)用。
-
harbor.globalname(name, handle)?注冊(cè)一個(gè)全局名字。如果 handle 為空,則注冊(cè)自己。skynet.name 和 skynet.register 是用其實(shí)現(xiàn)的。
你可以利用這組 api 來(lái)解決做一次跨節(jié)點(diǎn)遠(yuǎn)程調(diào)用,因?yàn)楣?jié)點(diǎn)斷開而無(wú)法收到回應(yīng)的問題。注意:link 和 linkmaster 都有一定的開銷,所以最好在一個(gè)節(jié)點(diǎn)中只用少量服務(wù)調(diào)用它來(lái)監(jiān)控組網(wǎng)狀態(tài)。由它再來(lái)分發(fā)到業(yè)務(wù)層。
對(duì)于 harbor id 不可復(fù)用的問題。你可以在?Config?中將 harbor 配置為引用一個(gè)系統(tǒng)環(huán)境變量。然后給 skynet 編寫一個(gè)啟動(dòng)腳本,利用一個(gè)額外的程序去某個(gè)管理器中獲得尚未使用過的 harbor id ,設(shè)入環(huán)境變量,再啟動(dòng) skynet 進(jìn)程。這些 skynet 沒有給出現(xiàn)成的解決方案,需要你自己來(lái)實(shí)現(xiàn)。
cluster 模式
skynet 提供了更具彈性的集群方案。它可以和 master/slave 共存。也就是說(shuō),你可以部署多組 master/slave 網(wǎng)絡(luò),然后再用 cluster 將它們聯(lián)系起來(lái)。當(dāng)然,比較簡(jiǎn)單的結(jié)構(gòu)是,每個(gè)集群中每個(gè)節(jié)點(diǎn)都配置為單節(jié)點(diǎn)模式(將 harbor id 設(shè)置為 0)。
要使用它之前,你需要編寫一個(gè) cluster 配置文件,配置集群內(nèi)所有節(jié)點(diǎn)的名字和對(duì)應(yīng)的監(jiān)聽端口。并將這個(gè)文件事先部署到所有節(jié)點(diǎn),并寫在?Config?中。這個(gè)配置文件的范例見 examples/clustername.lua :
db = "127.0.0.1:2528"這表示,集群中定義有一臺(tái)叫做 db 的節(jié)點(diǎn),通訊端口為 127.0.0.1:2528 。
接下來(lái),你需要在 db 的啟動(dòng)腳本里寫上?cluster.open "db"?。示例見 examples/cluster1.lua 。
local skynet = require "skynet" local cluster = require "cluster"skynet.start(function()local sdb = skynet.newservice("simpledb")skynet.name(".simpledb", sdb)print(skynet.call(".simpledb", "lua", "SET", "a", "foobar"))print(skynet.call(".simpledb", "lua", "GET", "a"))cluster.open "db" end)它啟動(dòng)了 simpledb 這個(gè)服務(wù),并起了一個(gè)本地名字 .simpledb ,然后打開了 db 節(jié)點(diǎn)的監(jiān)聽端口。
在 examples/cluster2.lua 中示范了如何調(diào)用 db 上的 .simpledb 服務(wù)。( .simpledb 原本是一個(gè)本地服務(wù),但通過 cluster 接口,其它節(jié)點(diǎn)也可以訪問到它。)
local skynet = require "skynet" local cluster = require "cluster"skynet.start(function()local proxy = cluster.proxy("db", ".simpledb")print(skynet.call(proxy, "lua", "GET", "a"))print(cluster.call("db", ".simpledb", "GET", "a")) end)有兩種方式可以訪問到 db.simpledb :
可以通過 cluster.call(nodename, service, ...) 提起請(qǐng)求。這里 nodename 就是在配置表中給出的節(jié)點(diǎn)名。service 可以是一個(gè)字符串,或者直接是一個(gè)數(shù)字地址(如果你能從其它渠道獲得地址的話)。當(dāng) service 是一個(gè)字符串時(shí),只需要是那個(gè)節(jié)點(diǎn)可以見到的服務(wù)別名,可以是全局名或本地名。但更推薦是 . 開頭的本地名,因?yàn)槭褂?cluster 模式時(shí),似乎沒有特別的理由還需要在那個(gè)節(jié)點(diǎn)上使用 master/slave 的架構(gòu)(全局名也就沒有特別的意義)。
可以通過 cluster.proxy(nodename, service) 生成一個(gè)本地代理。之后,就可以像訪問一個(gè)本地服務(wù)一樣,和這個(gè)遠(yuǎn)程服務(wù)通訊。不過還是要遵循 cluster 的要求:遠(yuǎn)程服務(wù)必須使用請(qǐng)求回應(yīng)模式(不可以沒有返回值);必須使用 lua 協(xié)議。
注意:你可以為同一個(gè) skynet 進(jìn)程(集群中的節(jié)點(diǎn))配置多個(gè)通道。這種策略有時(shí)會(huì)更有效。因?yàn)橐粋€(gè)通道僅由一條 TCP 連接保持通訊。如果你有高優(yōu)先級(jí)的集群間調(diào)用需要處理,那么單獨(dú)開一個(gè)通道可能更好些。
遠(yuǎn)端名字服務(wù)
你可以如上面一節(jié)所述的方式,給 skynet 的服務(wù)命名,然后使用字符串來(lái)替代數(shù)字地址。同時(shí),cluster 還提供另一套命名方案。
在本地進(jìn)程內(nèi)調(diào)用?cluster.register(name [,addr])?可以把 addr 注冊(cè)為 cluster 可見的一個(gè)字符串名字 name 。如果不傳 addr 表示把自身注冊(cè)為 name 。
遠(yuǎn)端可以通過調(diào)用?cluster.query(node, name)?查詢到這個(gè)名字對(duì)應(yīng)的數(shù)字地址。如果名字不存在,則拋出 error 。
由于歷史原因,這套命名方案和上一節(jié)的方案并存。但這節(jié)描述的方案更為推薦。因?yàn)檫@套命名體系僅在 cluster 的模塊中存在,并不影響 skynet 底層的命名系統(tǒng),更容易為日后擴(kuò)展。而 skynet 底層的命名系統(tǒng)已不再推薦使用。
Cluster 配置更新
Cluster 是去中心化的,所以需要在每臺(tái)機(jī)器上都放置一份配置文件(通常是相同的)。通過調(diào)用 cluster.reload 可以讓本進(jìn)程重新加載配置。如果你修改了每個(gè)節(jié)點(diǎn)名字對(duì)應(yīng)的地址,那么 reload 之后的請(qǐng)求都會(huì)發(fā)到新的地址。而之前沒有收到回應(yīng)的請(qǐng)求還是會(huì)在老地址上等待。如果你老的地址已經(jīng)無(wú)效(通常是主動(dòng)關(guān)閉了進(jìn)程)那么請(qǐng)求方會(huì)收到一個(gè)錯(cuò)誤。
在線上產(chǎn)品中如何向集群中的每個(gè)節(jié)點(diǎn)分發(fā)新的配置文件,skynet 并沒有提供方案。但這個(gè)方案一般比較容易實(shí)現(xiàn)。例如,你可以自己設(shè)計(jì)一個(gè)中心節(jié)點(diǎn)用來(lái)管理它?;蛘咦屜到y(tǒng)管理員編寫好同步腳本,并給程序加上控制指令來(lái)重載這些配置。
Cluster 和?Snax?服務(wù)
如果你使用?Snax?框架編寫服務(wù),且服務(wù)需要通過 Cluster 調(diào)用。那么需要做一些額外的工作。
首先,在?Snax?服務(wù)的 init 函數(shù)里,請(qǐng)調(diào)用一次?snax.enablecluster()?,否則它無(wú)法響應(yīng) Cluster 調(diào)用,而只能接收本地調(diào)用。
其次,你需要保證調(diào)用方和提供服務(wù)的機(jī)器上都能打開 snax 腳本。
如果全部條件滿足,那么你可以用?cluster.snax(node, name [,address])?來(lái)生成一個(gè)遠(yuǎn)程 snax 服務(wù)對(duì)象。
當(dāng)你只給出 node 和 name 時(shí),相當(dāng)于去目標(biāo) node 上使用 snax.queryservice 獲取一個(gè)服務(wù)名對(duì)應(yīng)的地址;如果給出了第三個(gè)參數(shù) address ,那么 address 就是 snax 服務(wù)的地址,而 name 則是它的服務(wù)類型( 綁定 snax 服務(wù)需要這個(gè)類型,具體見 snax.bind )。
總結(jié)
以上是生活随笔為你收集整理的Cluster 集群的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL InnoDB Cluster
- 下一篇: 安卓动态调试七种武器之离别钩 – Hoo