简介
skynet-x
是基于actor
消息的服务框架,那么我们需要定义一套标准且高效的消息结构
Processor
一个伪线程的逻辑处理器概念,它分为独占和负载两种模式。
-
独占
Processor
是为了更好的处理实时性更高的业务,它不会被其他任务抢占 -
负载
Processor
又可分为两种运行态,均匀的处理业务以及从其他Processor
上偷窃任务,尽量保证Processor
不会过于闲置,除此之外,负载Processor
可随着任务的变动而增加(不会超过最大设定值),特别的当某个任务陷入”死循环”或者是超出设定运行阈值的时候会重新创建一个Processor
,并让之前的挂起(在C版本中将会被强制关闭)。
C版本和Go版本调度和设计上差异不大,但一些细节上的处理可能不同,因为C可以提供更多的底层控制
PID
一个 message
最重要的是消息地址,如果一个消息没有地址的话我们称为 dead-letter
。 那么我们通过Pid
标定一个地址类型,
它表示该服务的唯一id (本质上是一个uint64
)的类习惯,它一定能确保在当前节点以及集群中唯一的。
在服务本身未被关闭的时候,pid
一定不会产生变动,但重新启动节点之后,它的值可能会发生改变,因为所有服务默认都是并发启动,除非手动指定了关系(这也是它与skynet-x
的区别),所以不要尝试保存这个pid
一旦能确定了一个pid
的话,就可以通过 skynet.send(pid,cmd,...) or skynet.call(ti,pid,cmd,...)
将其发送出去了。
服务的消息队列
Actor
模型最重要的的概念是 mailbox
,它代表了一个实体需要处理的队列容器,
得益于go
的简单性,可以使用 channel
来实现,但这种方式的实现性能不高,因为 channel
底层的结构使用的是互斥锁,
所以我采用了mpsc
实现了无锁队列,性能更优于 channel
TODO: 吞吐量对比
消息的接受和发送
- 发送
用户不需要构建这个结构体,仅仅需要指定 destination
以及需要发送的数据,而且 skynet-x
消息投递被设计成不允许发送 nil
因为这是无任何意义的,相反它还会消耗服务投递的性能,如果确实有这种需求,可以发送 struct{}{}
。
而且消息发送成功只能代表被 mailbox
接受了,不代表会被立即处理,而不会一定处理成功,所以需要正确理解这种方式。
如果发送失败,那么一定失败,并返回一个错误
- 接收
接受回调只包含5
个关键参数 context
,addr
,session
,mtype
,argument
-
context
其实就是创建服务用户指定的结构指针,用于数据传递和状态修改 -
session
主要的作用是用以区分这条消息是否是同步请求, 如若大于0,则其值就是请求序列号,只需要通过skynet.ret(msg)
返回即可 -
mtype
仅仅是一个消息类别的区分,类似于消息号,用户可自行定义,可作为rpc
消息类型 -
argument
才是真实的数据,它可以是任意值,特别的,在lua
中这个值是会被解构,在跨节点通讯这个值恒为[]byte
,当不需要时记得 skynet.free1.4.0 这个由底层回收,用户不用关心
异步消息
异步消息通过 skynet.send
的方式进行投递,它只在乎这个消息有没有正确到达到对点服务,而不关心是否能被对点服务正确处理,并返回一个 error
同步消息
同步消息通过 skynet.call
的方式进行投递,它会阻塞当前coroutine
,它也返回一个错误,能解除此次阻塞只有两个条件,对点服务skynet.ret
或者达到了指定超时时间,
超时一定需要大于
10ms
,这是内置计时器的最小精度,所以特别在远程通讯的时候要考虑到 i/o 的延时