前言
前不久尝试了 http 服务的搭建,现在为它搭建一个网关。看了 skynet wiki 中的几篇涉及 master/slave 的文章,以及 lualib/cmaster.lua 和 lualib/cslave.lua 的代码后,实现了第一个方案,gate 和 game 处于一个集群中,gate 和 game 间通过 slave/harbor 服务来间接通信。
思路
大致架构图:
设计方案:
- 利用 skynet 的 master/slave 机制,master 作为 gate,slave 作为 game。
- 单独一个集群作为 gate,另一个集群作为 game。
- 尝试 skynet 的 cluster 服务。
本文用第一个方案搭建。方案二 使用了 skynet 自带的 gate 服务,有兴趣可以看看。
目录结构
- 启动 gate:./skynet game/etc/config_web_master
- 启动 game:./skynet game/etc/config_web_slave
代码
masterweb.lua 和 slaveweb.lua 从 examples/simpleweb.lua 复制出来修改成两个服务。
-
config_web_master 作为集群的 master 的配置,以下几项需要关注:
harbor = 1 -- 集群中节点 id,0 被保留,1 ~ 255 可以使用 address = "127.0.0.1:2526" -- slave 服务的监听地址,即便是 master 服务所在进程,也会启动一个 slave 服务 master = "127.0.0.1:2013" -- master 服务的监听地址,用于 slave 连接 start = "service_web/masterweb" -- main script bootstrap = "snlua bootstrap" -- The service for bootstrap standalone = "0.0.0.0:2013" -- master 服务的监听地址,开放给 slave 来连接
-
config_web_slave 作为集群的 slave 的配置,以下几项需要关注:
harbor = 2 -- 集群中节点 id,与 master 拥有的 id 区分开 address = "127.0.0.1:2527" -- slave 服务的监听地址 master = "127.0.0.1:2013" -- master 服务的监听地址,用于 slave 连接 start = "service_web/slaveweb" -- main script bootstrap = "snlua bootstrap" -- The service for bootstrap --standalone = "0.0.0.0:2013" -- 注释掉,该项存在则表示是 master 服务
-
masterweb.lua 启动 gate 接收客户端连接,20个 agent 服务来承接连接,接收客户端数据,并且转发给 game 服务,然后展示 game 服务的返回结果。
local skynet = require "skynet" local socket = require "skynet.socket" local httpd = require "http.httpd" local sockethelper = require "http.sockethelper" local urllib = require "http.url" local table = table local string = string local mode, protocol = ... protocol = protocol or "http" if mode == "gate" then ... -- 省略没有修改的内容 game_svr_addr = game_svr_addr skynet.start(function() skynet.dispatch("lua", function (_,_,id) socket.start(id) local interface = gen_interface(protocol, id) if interface.init then interface.init() end -- limit request body size to 8192 (you can pass nil to unlimit) local code, url, method, header, body = httpd.read_request(interface.read, 8192) -- 1. 拿到 game 服务的地址 -- 发消息给 .cslave 获取地址 -- 2. 将 http 消息内容转发给 game -- 3. 将 game 回复的内容转发给 client LOG("find game_addr from slave begin") if not game_svr_addr then -- 向 .cslave 服务查询 game 服务地址 game_svr_addr = skynet.call(".cslave", "lua", "QUERYNAME", "game") end LOG("find game_addr from slave, game_addr,%s", game_svr_addr) local code, msg = skynet.call(game_svr_addr, "lua", code, url, method, header, body) --skynet.wait() LOG("callback, code,%s, url,%s", code, url) if code then response(id, interface.write, code, msg) else if url == sockethelper.socket_error then skynet.error("socket closed") else skynet.error(url) end end socket.close(id) if interface.close then interface.close() end end) end) else ... -- 省略没有修改的内容 end
-
slaveweb.lua 注册 lua 消息的处理函数,将 gate 发过来的 http 消息内容构造成显示数据回传给 gate 服务;启动后,在集群中注册自己为 game 服务,以便于 gate 可以通过 game 这个名字查询到远程服务。
local skynet = require "skynet" local table = table local string = string skynet.dispatch("lua", function (session, addr, code, url, method, header, body) LOG("i am slave, received a msg, session,%s, addr,%s", session, addr) local msg if code == 200 then local tmp = {} if header.host then table.insert(tmp, string.format("host: %s", header.host)) end local path, query = urllib.parse(url) table.insert(tmp, string.format("path: %s", path)) if query then local q = urllib.parse_query(query) for k, v in pairs(q) do table.insert(tmp, string.format("query: %s= %s", k,v)) end end table.insert(tmp, "-----header----") for k,v in pairs(header) do table.insert(tmp, string.format("%s = %s",k,v)) end table.insert(tmp, "-----body----\n" .. body) msg = table.concat(tmp,"\n") end skynet.ret(skynet.pack(code, msg)) end) skynet.start(function() skynet.send(".cslave", "lua", "REGISTER", "game", skynet.self()) end)
-
执行表现:
结语
借助这个简易 gate 的搭建,加深了对 master/slave 的了解。
- 后续实现方案2 和 方案3 的搭建。
- 目前是在 gate 中对 http 消息做了解析,后续将这一步放到 game 来做,gate 只做数据包的转发。