Treasure / message

Created Fri, 22 Jul 2022 00:00:00 +0000
1302 Words

简介

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.free 1.4.0 这个由底层回收,用户不用关心

异步消息

异步消息通过 skynet.send的方式进行投递,它只在乎这个消息有没有正确到达到对点服务,而不关心是否能被对点服务正确处理,并返回一个 error

同步消息

同步消息通过 skynet.call的方式进行投递,它会阻塞当前coroutine,它也返回一个错误,能解除此次阻塞只有两个条件,对点服务skynet.ret 或者达到了指定超时时间,

超时一定需要大于10ms,这是内置计时器的最小精度,所以特别在远程通讯的时候要考虑到 i/o 的延时