skynet 中隐藏的单点服务 : Harbor
昨晚和同事一起看 skynet 的代码,对于 skynet 实现的按名字发送消息比较好奇,走读了一下代码,特意写篇文章记录一下
Skynet 服务间通信,比较常用的是 skynet.send
,该接口在 skynet/lualib/skynet.lua
中,实现则是 skynet.core
模块的 send 方法。skynet.core
是 skynet 提供给 lua 的一个 so,代码在 skynet/lualib-src/lua-skynet.c
,它用了一个巧妙的注册办法:luaopen_skynet_core
,将自己注册成 skynet.core
了。它提供的 send 方法就是 C 代码里的 lsend
,也就是 send_message
方法。对于目标服务地址是字符串的,就会用 skynet_sendname
实现发送
skynet/lualib/skynet.lua:skynet.send
->skynet/lualib-src/lua-skynet.c:lsend
->skynet/lualib-src/lua-skynet.c:send_message
skynet_sendname
是来自于 skynet/skynet-src/skynet_server.c
的核心方法,猜测这里是用到哈希表之类的结构,将名字转换成整型 id 的。仔细看代码,如果字符串开头是 :
,那就是约定的 16 进制字符串地址;.
是本节点专用的名字,相当于局部变量,跨节点不可见的,skynet_handle_findname
这个方法就是查找哈希表的,符合预期。至于通用的字符串名字,就走最后一个 else 语句了
可以看到, 这里 构造了一条远程消息,然后以 skynet_harbor_send
丢给 harbor 服务了。该接口在 skynet/skynet-src/skynet_harbor.c
,只是转用了 skynet_context_send
以 PTYPE_SYSTEM
的方式发出去而已。这个操作就是往 service_harbor.c
的消息队列里塞了一个消息
service_harbor.c
是一个纯 C 服务,代码在 skynet/service-src/service_harbor.c
。处理逻辑都在 mainloop
函数里,对于 PTYPE_SYSTEM
类型的消息,处理如下:
case PTYPE_SYSTEM : {
// remote message out
const struct remote_message *rmsg = msg;
if (rmsg->destination.handle == 0) {
if (remote_send_name(h, source , rmsg->destination.name, rmsg->type, session, rmsg->message, rmsg->sz)) {
return 0;
}
} else {
if (remote_send_handle(h, source , rmsg->destination.handle, rmsg->type, session, rmsg->message, rmsg->sz)) {
return 0;
}
}
skynet_free((void *)rmsg->message);
return 0;
}
看起来是通过 remote_send_name
做查找了,果然,第一行就是 hash_search
函数。同节点的服务启动时,会调用 cdummy
服务(cluster 组网模式)的 register 接口,最终落到 skynet/service-src/service_harbor.c
的 update_name
,将名字注册好,所以 hash_search
就能找到最终服务的地址,以 remote_send_handle
的方式发送出去
目前项目里都是用 cluster 模式组网,跨节点通讯都会丢到 clusterd 再派发到其他服务,远程部分的处理在 cluster 完成,本地部分就用 harbor 了。所以所有用名字做目标地址的调用,还是会经过 harbor
服务做名字转整型地址的操作,这里是一个单点,有可能有性能问题。还是建议各自服务缓存远端的 id 地址,后续发送不再做转换操作了,参见 skynet 的 wiki