skynet集群 --- master/slave 模式
skynet本身解决的核心问题是充分利用同一台机器的多核的处理能力。云风在描述集群时,强调说skynet只提供了构建集群的组件。那是因为不是所有项目遇到的问题都能够用统一的解决方案的。还提出任何企图抹平服务运行位置差异的设计都需要慎重考虑,很可能存在设计问题,因为集群协作不与单机多服务工作,集群中可能对方的服务并未启动,而单机工作中,可以认为不会只有一部分出错(即不会说当前功能正在运行,但是本机目标服务未启动的情况)。
skynet自带两种集群基础架构,我们接下来要拜读的是master/slave 方式。
-- examples/config ... harbor = 1 address = "127.0.0.1:2526" master = "127.0.0.1:2013"
standalone = "0.0.0.0:2013"
...
如上代码段所示是skynet例子中的config配置表,如需要使用master/slave的集群模式,只需要配置上面4个字段即可。
集群中 harbor 字段,表示集群机子的id,由8位整数表示,其中0代表不启用master/slave 集群模式,所以一个集群中,只能有255个slave节点。在一个skynet启动的服务地址中,服务地址可以用32位的整数表示,其中高8位即代表harbor值,剩下的24位即是单机启动的service占用。
address 则表示节点slave监听的ip与端口。
master 则表示用来维护slave节点间的关系的服务。
standalone 表示当前机子会启动master服务。
master/slave 是用来处理单机器运算能力不足的补充,所以忽略了集群间故障的处理机制,即当一台机器出错未启动或者崩了的情况下,整个集群会出现问题,即master/slave 是默认整个集群都是正常运作的。
接下来追踪整个master/slave 是怎么启动的,并且多个slave是怎么交互的,master主要起到什么功能,service_harbor服务又是怎么工作的,service_harbor在实现时可能出现什么问题?
master/slave 的启动
master/salve 的启动是由bootstrap服务执行的,首先先判断harbor是否大于0,大于0则表示启用master/slave 集群模式,然后先判断standalone来确定当前机器是否需要启动master服务,最后启动slave节点。
master的启动如下,会监听master配置的ip与端口。

1 -- cmaster.lua 2 skynet.start(function() 3 local master_addr = skynet.getenv "standalone" 4 skynet.error("master listen socket " .. tostring(master_addr)) 5 local fd = socket.listen(master_addr) 6 socket.start(fd , function(id, addr) 7 skynet.error("connect from " .. addr .. " " .. id) 8 socket.start(id) 9 local ok, slave, slave_addr = pcall(handshake, id) 10 if ok then 11 skynet.fork(monitor_slave, slave, slave_addr) 12 else 13 skynet.error(string.format("disconnect fd = %d, error = %s", id, slave)) 14 socket.close(id) 15 end 16 end) 17 end)
master启动完成后,会启动当前机子的slave节点,slave启动过程如下。
首先slave会listen一个端口,然后会连接master服务建立连接,并且启动service_harbor服务,之后slave会发送 'H harbor_id harbor_address" 消息给master服务,master服务执行 handshake 函数(此函数通知已有slave节点们去分别跟新的slave节点进行连接并且返回 “W n" 已有节点数量给新的slave节点)最后master会启动一个协程来监听该slave节点的通讯(主要是处理该新节点所需要创建的全局服务名,以及该节点对全局服务名的一些操作,例如查询),salve等待master的"W n"消息获得已有节点数量,之后slave节点会启动一个协程来处理与master的通讯(主要是监听新slave节点的连接,全局服务名的注册,断开连接等操作),最后会等待n个已有节点来连接当前slave节点,连接完成后,slave服务执行在启动期间创建的任务队列(主要是有其他新节点上来,slave启动完成后需要去连接新节点),并且将获取到的global_name 转发给service_harbor 服务。
至此,slave节点启动完毕。

1 -- cslave.lua 2 3 skynet.start(function() 4 local master_addr = skynet.getenv "master" 5 local harbor_id = tonumber(skynet.getenv "harbor") 6 local slave_address = assert(skynet.getenv "address") 7 local slave_fd = socket.listen(slave_address) 8 skynet.error("slave connect to master " .. tostring(master_addr)) 9 local master_fd = assert(socket.open(master_addr), "Can't connect to master") 10 11 skynet.dispatch("lua", function (_,_,command,...) 12 local f = assert(harbor[command]) 13 f(master_fd, ...) 14 end) 15 skynet.dispatch("text", monitor_harbor(master_fd)) 16 17 harbor_service = assert(skynet.launch("harbor", harbor_id, skynet.self())) 18 19 local hs_message = pack_package("H", harbor_id, slave_address) 20 socket.write(master_fd, hs_message) 21 local t, n = read_package(master_fd) 22 assert(t == "W" and type(n) == "number", "slave shakehand failed") 23 skynet.error(string.format("Waiting for %d harbors", n)) 24 skynet.fork(monitor_master, master_fd) 25 if n > 0 then 26 local co = coroutine.running() 27 socket.start(slave_fd, function(fd, addr) 28 skynet.error(string.format("New connection (fd = %d, %s)",fd, addr)) 29 socketdriver.nodelay(fd) 30 if pcall(accept_slave,fd) then 31 local s = 0 32 for k,v in pairs(slaves) do 33 s = s + 1 34 end 35 if s >= n then 36 skynet.wakeup(co) 37 end 38 end 39 end) 40 skynet.wait() 41 socket.close(slave_fd) 42 else 43 -- slave_fd does not start, so use close_fd. 44 socket.close_fd(slave_fd) 45 end 46 skynet.error("Shakehand ready") 47 skynet.fork(ready) 48 end)
在启动slave时,会启动 service_harbor 的 c 服务。
service_harbor 服务主要处理发送给一个服务的分发,判断是否需要走集群发送。
当业务逻辑需要发送消息时,会先判断是发送到本机service还是到集群的service,如果是本机service,直接将消息插入到目标service的次级消息队列中,如果是集群的service,需要会先将消息插入到本机service_harbor 的服务当中,然后在service_harbor服务当中进行分发。
这里有一点需要注意的是,如果目标service是在刚跟本机slave进行握手,即当前在service_harbor中的slave状态是 STATUS_HANDSHAKE 时,会先将消息插入到目标slave的队列当中,等到目标slave完成与本机slave的socket建立后,然后对目标slave的队列的消息一一处理。

1 // skynet_server.c 2 int 3 skynet_send(struct skynet_context * context, uint32_t source, uint32_t destination , int type, int session, void * data, size_t sz) { 4 ... 5 if (skynet_harbor_message_isremote(destination)) { 6 struct remote_message * rmsg = skynet_malloc(sizeof(*rmsg)); 7 rmsg->destination.handle = destination; 8 rmsg->message = data; 9 rmsg->sz = sz & MESSAGE_TYPE_MASK; 10 rmsg->type = sz >> MESSAGE_TYPE_SHIFT; 11 skynet_harbor_send(rmsg, source, session); 12 } else { 13 struct skynet_message smsg; 14 smsg.source = source; 15 smsg.session = session; 16 smsg.data = data; 17 smsg.sz = sz; 18 19 if (skynet_context_push(destination, &smsg)) { 20 skynet_free(data); 21 return -1; 22 } 23 } 24 return session; 25 }
以上即是基本的master/slave集群方式的启动并且通信逻辑。
多个slave是怎么交互的
当有消息需要发送给集群服务时,首先先判断是否为本机服务消息,如果是集群服务的,先将消息插入到本机的service_harbor服务当中,然后再service_harbor进行分发处理,如果目标slave未完成与本机的连接建立,那么先将消息插入到目标slave的消息队列当中,等待目标slave与本机完成连接时,再对消息队列的消息分发处理。
master主要起到什么功能
master服务在此中的作用,主要是监听各slave的消息,有新slave连接上来时会通知集群中的slave与新slave建立连接。另外会管理全局服务的创建与映射等。
service_harbor在实现时可能出现什么问题
service_harbor 在实现时,可能会出现目标节点仍未建立连接完成,就有消息要发送给目标节点,这时需要先让这些消息保存起来,等连接建立完成后,再处理对应消息。
该模式的隐患
该模式主要是为了解决单机cpu运算能力不足的补充,所以对集群的支持可能没那么完善。该模式默认集群中的所有节点都是正常工作并且稳定通讯的,否则整个集群工作都会出现问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)