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的加载文件

你可能感兴趣的:(skynet cluster部署记录笔记)