昨晚和同事一起看 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_sendPTYPE_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.cupdate_name,将名字注册好,所以 hash_search 就能找到最终服务的地址,以 remote_send_handle 的方式发送出去

目前项目里都是用 cluster 模式组网,跨节点通讯都会丢到 clusterd 再派发到其他服务,远程部分的处理在 cluster 完成,本地部分就用 harbor 了。所以所有用名字做目标地址的调用,还是会经过 harbor 服务做名字转整型地址的操作,这里是一个单点,有可能有性能问题。还是建议各自服务缓存远端的 id 地址,后续发送不再做转换操作了,参见 skynet 的 wiki