这里记录一下从单一节点服务器到多节点服务器的大致变化过程。对应的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的配置也可以单独配置成一个文件,比如:
__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的加载文件