skynet cluster部署记录笔记

这里记录一下从单一节点服务器到多节点服务器的大致变化过程。对应的skynet版本.

虽然skynet的examples中有cluster1.lua和cluster2.lua的例子,但是实际使用的时候感觉还是不够全面。这里我就重新编写一下,加上自己理解。

首先看下我的中心服务器的配置文件:

config.cluster1

include "config.path"

nodename = "cluster_center" --节点服务名称。
thread = 4 --thread 启动多少个工作线程。通常不要将它配置超过你实际拥有的 CPU 核心数

--[[
skynet 由一个或多个进程构成,每个进程被称为一个 skynet 节点。

单节点和master/slave的配置说明:
1. 单节点:
    如果我们只是想配置一个单节点服务,即只有一个物理机,上面部署各种service,我们只需要把 harbor置为0,并将standalone置为nil即可
    master和address这两个不用配置,直接置为nil即可。
2. master/slave:
    由于harbor最大为255,并且不能小于1,这决定了我们slave最多是255个物理机组网,并且会选择其中一个slave作master。

    slave配置:
        首先配置harbor,并且指定standalone为nil,配置master的地址(slave会尝试连接这个中心地址),
        最后配置address地址(这是每个slave独立地址),master会通过(harbor和address)区分不同的slave
    master配置:
        其他配置都同slave配置一样,唯一不同的是:充当master的slave必须配置standalone,这是监听所有slave的地址。
        该配置需要与之前配置的master地址一样,否则各个slave无法连接到master。

    如果一个 slave 意外退出网络,这个 harbor id 就被废弃,不可再使用。这样是为了防止网络中其它服务还持有这个断开的 slave 上的服务地址;
    而一个新的进程以相同的 harbor id 接入时,是无法保证旧地址和新地址不重复的。
    也就是说该模式下,已经用过的harbor无法再次使用,所以这个模式,并不能实现热切换。只能充当单个物理机压力的分担。

    不过skynet有提供cluster模式,可以部署多组 master/slave 网络,然后再用 cluster 将它们联系起来,
    
3.如果只想配置cluster的话,只要配置harbor=0即可,会自动忽略master和address指定的值。
--]]
harbor = 0
--指定监听的内网端口地址。如果harbor为0,必须指定nil;如果为其他值并且本节点是master节点,需要指定地址。
standalone = nil --"0.0.0.0:4000"
--指定skynet控制中心的地址和端口,如果你配置了 standalone 项,那么这一项通常和 standalone 相同。
master = "0.0.0.0:4000"
--当前skynet节点的地址和端口,方便其它节点和它组网。注:即使你只使用一个节点,也需要开启控制中心,并额外配置这个节点的地址和端口
address = "172.16.200.43:4001"  --linux查看ip地址 ifconfig -a

--[[
下面是 skynet 节点的启动流程介绍。
skynet 节点通过运行 skynet 主程序启动,必须在启动命令行传入一个 Config 文件名作为启动参数。
skynet 会读取这个 config 文件获得启动需要的参数。
第一个启动的服务是 logger ,它负责记录之后的服务中的 log 输出。logger 是一个简单的 C 服务,
skynet_error 这个 C API 会把字符串发送给它。在 config 文件中,logger 配置项可以配置 log 输出的文件名,
默认是 nil ,表示输出到标准输出。
bootstrap 这个配置项关系着 skynet 运行的第二个服务。通常通过这个服务把整个系统启动起来。
默认的 bootstrap 配置项为 "snlua bootstrap" ,这意味着,skynet 会启动 snlua 这个服务,
并将 bootstrap 作为参数传给它。snlua 是 lua 沙盒服务,bootstrap 会根据配置的 luaservice 匹配到最终的 lua 脚本。
如果按默认配置,这个脚本应该是 service/bootstrap.lua
]]
bootstrap = "snlua bootstrap"
start = "main"  -- 用户自定义启动的脚本入口,地址寻址在luaservice设置的目录列表
--preload = "./lualib/preload.lua"  -- run preload.lua before every lua service run

--[[
root="./"  --指定工作目录
luaservice = "./service/?.lua;./skynet/service/?.lua;./lualib/?.lua" --寻址对于的lua文件的目录列表
lualoader = "./skynet/lualib/loader.lua" --默认的加载器
lua_path = "./service/?.lua;./lualib/?.lua;./skynet/lualib/?.lua;./skynet/lualib/?/init.lua"
lua_cpath = "./luaclib/?.so;./skynet/luaclib/?.so"
--cpath 用 C 编写的服务模块的位置,通常指 cservice 下那些 .so 文件。如果你的系统的动态库不是以 .so 为后缀,
--需要做相应的修改。这个路径可以配置多项,以 ; 分割
cpath = "./skynet/cservice/?.so"
--]]
profile = true
-- daemon = "./skynet.pid"

config.path

root = "./"
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua;"..root.."test/?/init.lua"
lualoader = root .. "lualib/loader.lua"
lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua"
lua_cpath = root .. "luaclib/?.so"
snax = root.."examples/?.lua;"..root.."test/?.lua"
cpath = root.."cservice/?.so"

config.cluster2

几乎与config.cluster1一样,只是改了nodename

nodename = "cluster_gate1" --节点服务名称。

main.lua核心部分:

skynet.start(
        function()
            --[[
            skynet.name(".cslave", skynet.newservice("xxx")) 只能用于本节点名字命名,不能用于全局服务名
            skynet.register(".cslave"、"cslave") 注册本服务的名字
            service起名字注意事项,如果是只服务于本节点的,最好名字前面带点表示本地节点服务,不能被其他节点调用(如果调用会报错)
            如果需要被其他slave节点调用,可以前面不带点。
            名字总长度不能超过16

            当两个slave节点都拥有相同名字的service的时候,优先调用本地节点的服务
            --]]

            --print("1 & 2  = ", 1 & 2) --按位与
            --print("1 | 2  = ", 1 | 2) --按位或
            --print("1 ~ 2  = ", 1 ~ 2) --按位异或
            --print(" ~ 2   = ", ~2) --按位非
            --print("2 << 1 = ", 2 << 1) --左移
            --print("2 >> 1 = ", 2 >> 1) --右移
            --for k, v in pairs(math) do
            --    print(k, v)
            --end

            local nodeName = skynet.getenv("nodename") --这个nodename配置只是配置了环境
            local isMaster = nodeName  == "cluster_center"
            if isMaster then
                --[[
                __nowaiting解释:当一个名字没有配置在配置表中时,如果你向这个未命名节点发送一个请求,skynet 的默认行为是挂起,
                一直等到这个名字对应项的配置被更新。你可以通过配置节点名字对应的地址为 false 来明确指出这个节点已经下线。
                另外,可以通过在配置表中插入 __nowaiting = true 来关闭这种挂起行为

                要想别的结点能访问到我们的本地service,,我们需要打开指定的端口。比如tunnel1和tunnel2
                同时在其他节点上配置可供外网访问的ip-port,这样其他节点就能访问到我们这里的service了

                同样道理,要是本结点希望访问其他节点的服务,则对应节点也必须提供外网访问的ip-port比如tunnel3

                如果某个结点不需要给其他节点访问,那么就不需要配置监听端口
                --]]
                cluster.reload({
                    __nowaiting = true, --解释见上面,这样做的意义是防止写了错误的名字。
                    tunnel1 = "0.0.0.0:20003", --这里设置监听地址。master监听通道1(普通通道)
                    tunnel2 = "0.0.0.0:20004", --master监听通道2(优先通道),两个通道的原因是因为有些消息具有较高的优先级
                    tunnel3 = "需要配置你的网关IP地址:20003", --代理节点1
                })

                --这里打开指定端口,这样外网就能访问到这里了。
                cluster.open("tunnel1") --尝试监听端口
                cluster.open("tunnel2")

                --[[
                cluster.call 函数调用的第一个参数表示你想走的通道,切忌调用自己监听的通道,这样会报错。
                所以如果你要布局一个网络比如下图:

                                            中心节点

                                  //           ||           \\
                               网关1          网关2         网关3
                    假定每个网关跟中心节点都有交互。
                    那么中心节点的配置文件:
                    {
                        __nowaiting = false,
                        center1 = "0.0.0.0:20003", --中心节点1:普通通道
                        center1 = "0.0.0.0:20004", --中心节点2:优先通道。
                        gate1 = "网关1外网地址:20003", --网关1
                        gate2 = "网关2外网地址:20003", --网关2
                        gate3 = "网关3外网地址:20003", --网关3
                    }
                    网关1的配置:
                    {
                        __nowaiting = false,
                        center1 = "中心外网IP地址:20003", --中心节点1:普通通道
                        center1 = "中心外网IP地址:20004", --中心节点2:优先通道。
                        gate1 = "0.0.0.0:20003", --网关1
                    }
                    网关2的配置:
                    {
                        __nowaiting = false,
                        center1 = "中心机器的外网IP地址:20003", --中心节点1:普通通道
                        center1 = "中心机器的外网IP地址:20004", --中心节点2:优先通道。
                        gate2 = "0.0.0.0:20003", --网关2
                    }
                    网关3的配置:
                    {
                        __nowaiting = false,
                        center1 = "中心机器的外网IP地址:20003", --中心节点1:普通通道
                        center1 = "中心机器的外网IP地址:20004", --中心节点2:优先通道。
                        gate3 = "0.0.0.0:20003", --网关3
                    }

                    网关1调用中心节点:
                        cluster.call("center1","@注册名","方法名",参数。。。) 必须是 @ + 注册名
                    中心节点调用网关1:
                        cluster.call("gate1","@注册名","方法名",参数。。。)
                    网关2和网关3跟网关1类似。
                --]]
            else
                cluster.reload({
                    __nowaiting = false,
                    tunnel1 = "需要你的配置中心外网地址:20003", --这里设置中心节点地址1。
                    tunnel2 = "需要你的配置中心外网地址:20004", --这里设置中心节点地址2。
                    tunnel3 = "0.0.0.0:20003", --代理结点1的监听本地端口
                })
                --打开代理监听的端口地址
                cluster.open("tunnel3") --尝试监听端口
            end

            local theService = skynet.newservice("glpTestCluster")
               
            skynet.exit()
        end
)

main.lua是自定义的启动文件,里面就是根据config中配置的nodename来确定当前节点是否是中心节点而已,然后再用cluster加载不同的配置(具体配置描述已经写在代码注释中了)。最后中心节点和网关都启动了一个叫 glpTestCluster的服务,后面就是skynet.exit()结束自己。

网关也打开监听地址的原因:

如果只有中心节点监听的两个通道,那么中心节点无法给网关推送消息,只有被动的等网关消息的到来,然后返回一些数据。而我们要做的就是双向通信——网关可以给中心节点推消息,中心节点也可以直接给网关推消息。值得一提的是,如果某个网关失去跟中心节点的连接,等到下一次中心节点要给这个网关推消息的时候,skynet已经做了重连的机制。我们要做的就是推消息失败的错误处理。

再看一下那个服务:glpTestCluster.lua

local skynet = require "skynet"
require "skynet.manager"
local snapshot = require "snapshot"
local cjson = require "cjson"
local cluster = require "skynet.cluster"

local isMaster = false

function clusterPrint(...)
    print("glpTestCluster [" .. skynet.address(skynet.self()) .. "] " .. os.date("%Y-%m-%d %X") .. ']:', ...)
end

local Funcs = {}
function Funcs.testSelf(agent)
    clusterPrint("testSelf agent==self", agent == skynet.self(), skynet.address(agent))
end

function Funcs.test(val)
    clusterPrint("test val=", val)
    return val
end

function Funcs.testA(val, usr)
    clusterPrint("testA usr=", cjson.encode(usr), "val=", val)

    if isMaster then
        skynet.fork(function()
            skynet.sleep(100)
            --调用cluster.call的时候,如果该通道暂不可能的时候会报错。
            local status, ret = pcall(function()
                return cluster.call("tunnel3", "@fish1", "testA", 3, {
                    uid = 10002,
                    name = "美国人"
                })

            end)
            clusterPrint("status=", status, "ret=", ret)
        end)
    end

    return val
end

--测试cluster组网。
skynet.start(
        function()
            clusterPrint("skynet.start glpTestCluster")
            isMaster = skynet.getenv("nodename") == "cluster_center"

            --部署分布式节点的时候,这里的dispatch函数的回调跟普通的不一样。
            skynet.dispatch(
                    "lua",
                    function(session, source, funcName, ...)
                        clusterPrint("test dispatch", session, source, funcName, ...)
                        local f = Funcs[funcName]
                        if f then
                            skynet.retpack(f(...))
                        else
                            clusterPrint("Error no function =", funcName)
                        end
                    end
            )

            local f
            if isMaster then
                --如果这里没有注册fish服务名。则另一结点调用cluster.call会报错
                cluster.register("fish")
            else
                cluster.register("fish1")
                f = function()
                    clusterPrint("call the tunnel1")
                    --[[
                        这里的node就是指 cluster.reload中指定的名字。
                        还有这里的address名字前面需要有前缀@不然无法正确访问到另一个cluster的服务
                        远程调用的时候不需要再传递类型了"lua",默认传递lua类型的消息
                    ]]

                    --调用cluster.call的时候,如果该通道暂不可能的时候会报错。最好的做法就是用pcall括起来。
                    local status, ret = pcall(function()
                        return cluster.call("tunnel1", "@fish", "testA", 1, {
                            uid = 10001,
                            name = "中国人"
                        })

                    end)
                    clusterPrint("status=", status, "ret=", ret)

                    --测试访问本节点以外本地服务(服务名字开头带点)->结果失败报错,没有该服务
                    --带点的名字的本地服务必须是本地存在,否则会报错。
                    --local ret2 = skynet.call(".Test", "lua", "testB", 2)
                    --clusterPrint("ret2=", ret2)
                    skynet.timeout(300, f)
                end
            end

            if f then
                f()
            end
        end
)

里面内容也很简单就是用网关先去走tunnel1通道,调用中心节点注册的fish服务的testA方法,testA方法判断当前是否是中心节点,是的话就fork一个新协程走tunnel3通道,调用网关的注册的fish1服务的testA方法。

最后看一下实际效果


cluster应用的实际打印
左边是当做中心节点,右边是网关

打印结果非常完美~~~


cluster单独配置文件

cluster的配置也可以单独配置成一个文件,比如:

__nowaiting = true 
tunnel1= "0.0.0.0:6001" --这里设置监听地址。master监听通道1
tunnel2= "0.0.0.0:6002" --master监听通道2
gate1 = "192.168.0.8:10011" --网关1

同时在config.cluster1中增加一行

cluster = "./cluster-center.lua" --指定cluster reload的加载文件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342