skynet源码分析之cluster集群模式

比起slave/harbor集群模式,skynet提供了用的更为广泛的cluster集群模式,参考官方wiki https://github.com/cloudwu/skynet/wiki/Cluster。cluster模式利用socketchannel库(http://www.cnblogs.com/RainRill/p/8892648.html) 与其他skynet进程进行交互,每个请求包带一个唯一的session值,对端回应包附带session值。cluster集群模式tcp通道是单向的,即skynet进程1(集群中的节点)通过tcp通道向进程2发送请求包,进程2回应包也走这一通道。但是,进程2向进程1发送请求包及进程1的回应包则是另一条tcp通道。

每个集群节点都有一份完整的cluster配置,会启动一个clusterd的服务,调用loadconfig加载配置。

 第11-19行,加载配置文件(也可以手动传入配置table tmp)

第20-24行,保存节点名-地址映射关系

 1 -- service/clusterd.lua
 2  skynet.start(function()
 3      loadconfig()
 4      skynet.dispatch("lua", function(session , source, cmd, ...)
 5          local f = assert(command[cmd])
 6          f(source, ...)
 7      end)
 8  end)
 9  
10  local function loadconfig(tmp)
11      if tmp == nil then
12          tmp = {}
13          if config_name then
14              local f = assert(io.open(config_name))
15              local source = f:read "*a"
16              f:close()
17              assert(load(source, "@"..config_name, "t", tmp))()
18          end
19      end
20      for name,address in pairs(tmp) do
21          ...
22          node_address[name] = address
23          ...
24      end
25  end

以skynet进程1的A服务向skynet进程2的B服务发送请求包及回应为例,说明cluster的工作流程:

 对于进程2,配置了 db = "127.0.0.1:2528"启动后调用cluster.open "db"。

第4行,给clusterd服务发送消息。

第12-15行,启动一个gate服务,然后通知gate服务监听配置的地址。gate调用socket.listen监听外部socket连接。

第20行,watchdog就是clusterd服务的地址。

 1 -- lualib/skynet/cluster.lua
 2 function cluster.open(port)
 3     if type(port) == "string" then
 4         skynet.call(clusterd, "lua", "listen", port)
 5     else
 6         skynet.call(clusterd, "lua", "listen", "0.0.0.0", port)
 7     end
 8 end
 9 
10 -- service/clusterd.lua
11 function command.listen(source, addr, port)
12     local gate = skynet.newservice("gate")
13     ...
14     skynet.call(gate, "lua", "open", { address = addr, port = port })
15     skynet.ret(skynet.pack(nil))
16 end
17 
18 -- servcice/gate.lua
19 function handler.open(source, conf)
20     watchdog = conf.watchdog or source
21 end

对于进程1,调用cluster.call(db, "A", ...),给节点名为db(进程2)的A服务发送请求,最终调用到send_request

第9行,请求包带上唯一的sesssion值

第11行,按cluster定义的模式打包数据

第15行,获取socketchannel对象,如果第一次请求,会先创建socketchannel对象,并建立tcp连接

第16行,调用socketchannel的request接口发送请求包

 1 -- lualib/skynet/cluster.lua
 2 function cluster.call(node, address, ...)
 3     -- skynet.pack(...) will free by cluster.core.packrequest
 4     return skynet.call(clusterd, "lua", "req", node, address, skynet.pack(...))
 5 end
 6 
 7 -- service/clusterd.lua
 8 local function send_request(source, node, addr, msg, sz)
 9     local session = node_session[node] or 1
10     -- msg is a local pointer, cluster.packrequest will free it
11     local request, new_session, padding = cluster.packrequest(addr, session, msg, sz)
12     node_session[node] = new_session
13 
14     -- node_channel[node] may yield or throw error
15     local c = node_channel[node]
16 
17     return c:request(request, session, padding)
18 end
19 
20 function command.req(...)
21     local ok, msg = pcall(send_request, ...)
22     if ok then
23         ...
24         skynet.ret(msg)
25     end
26 end

创建socket对象时提供了response参数(第6行),所以是采用带session值的请求-回应模式。

第11行,协程阻塞在socket.read上,此时暂停co,等待回应包

 1 -- service/clusterd
 2     local host, port = string.match(address, "([^:]+):(.*)$")
 3     c = sc.channel {
 4         host = host,
 5         port = tonumber(port),
 6         response = read_response,
 7         nodelay = true,
 8     }
 9 
10 local function read_response(sock)
11     local sz = socket.header(sock:read(2))
12     local msg = sock:read(sz)
13     return cluster.unpackresponse(msg)      -- session, ok, data, padding
14 end

对于进程2,gate服务收到进程1的tcp连接请求后,

第8行,给clusterd服务发送消息

第17-18行,clusterd收到后,新建一个clusteragent服务。注:clusteragent是skynet最近新加的。参考https://blog.codingnow.com/2018/04/skynet_cluster.html#more

第24-28行,clusteragent服务专门处理进程1的cluster模式的请求。每个cluster节点连接都新建一个cluseteragent服务去处理请求包。

 1 -- service/gate.lua
 2 function handler.connect(fd, addr)
 3     local c = {
 4         fd = fd,
 5         ip = addr,
 6     }
 7     connection[fd] = c
 8     skynet.send(watchdog, "lua", "socket", "open", fd, addr)
 9 end
10 
11 -- service/clusterd.lua
12 function command.socket(source, subcmd, fd, msg)
13     if subcmd == "open" then
14         skynet.error(string.format("socket accept from %s", msg))
15         -- new cluster agent
16         cluster_agent[fd] = false
17         local agent = skynet.newservice("clusteragent", skynet.self(), source, fd)
18         cluster_agent[fd] = agent
19         ...     
20 end
21 
22 -- service/clusterdagent.lua
23 skynet.start(function()
24     skynet.register_protocol {
25         name = "client",
26         id = skynet.PTYPE_CLIENT,
27         unpack = cluster.unpackrequest,
28         dispatch = dispatch_request,
29     }
30     ...
31 end

当gate服务收到请求包后,转发给对应的clusteragent服务(第7行),

 1 -- service/gate.lua
 2 function handler.message(fd, msg, sz)
 3     -- recv a package, forward it
 4     local c = connection[fd]
 5     local agent = c.agent
 6     if agent then
 7         skynet.redirect(agent, c.client, "client", fd, msg, sz)
 8     else
 9         skynet.send(watchdog, "lua", "socket", "data", fd, netpack.tostring(msg, sz))
10     end
11 end

clusteragent服务消息分发函数dispatch_request,

第7-9行,如果是push请求,不需要回应,send给目的服务(B服务)后直接返回即可

第11行,如果是call请求,需要回应,给目的服务(B服务)发送消息,然后等待B服务处理完返回。

第14-21行,将消息打包成回应包,通过tcp返回给请求端(skynet进程1)。

进程1收到回应后,重启协程,返回结果给请求服务(A服务)。这就是cluster模式的调用流程。

 1 -- service/clusteragent.lua
 2 local function dispatch_request(_,_,addr, session, msg, sz, padding, is_push)
 3     if cluster.isname(addr) then
 4         addr = register_name[addr]
 5     end
 6     if addr then
 7         if is_push then
 8             skynet.rawsend(addr, "lua", msg, sz)
 9             return  -- no response
10         else
11             ok , msg, sz = pcall(skynet.rawcall, addr, "lua", msg, sz)
12         end
13     if ok then
14         response = cluster.packresponse(session, true, msg, sz)
15         if type(response) == "table" then
16             for _, v in ipairs(response) do
17                 socket.lwrite(fd, v)
18             end
19         else
20             socket.write(fd, response)
21         end
22      ...
23 end

posted on 2018-04-21 19:44  RainRill  阅读(3858)  评论(1编辑  收藏  举报

导航