Back to Top

skynet 初探

公司立了个新项目,让我设计下服务器端,趁机尝试一下skynet。

skynet 优缺点

传统的服务器是单线程,多进程的拓扑结构。 它按照逻辑分布,将玩家分散于不同的进程,游戏的主循环依次运行着各个子系统。它存在的问题是:当单核性能不足时,需要将一些逻辑从主循环中分出去。针对这个问题:

一种思路是为部分逻辑开启后台线程,使用命令队列通信,它存在的问题是:

  1. 如何合理的利用线程(什么时候开后台线程,每个逻辑有多少个后台线程,线程的切换代价);
  2. 开发耗脑,异步逻辑没有同步逻辑容易理解;
  3. 调试困难,无法还原完整调用堆栈(使用C++11的lamda表达式可以缓解)。

另一种思路是使用一些并发模型,如:Actor模型。

Actor可以理解成一个独立的实体,Actor之间不能直接调用,需要通过彼此发消息完成通信,使用这种模型设计逻辑可以先天的支持并发。 每个Actor可以清晰的定义输入输出及依赖,方便查错。

如果使用C/C++实现Actor模型,对于消息队列的处理,依旧存在着开发耗脑,调试困难的问题。

skynet为解决这个问题引入lua,每一个Actor是一个独立的lua虚拟机,使用lua协程封装异步调用,代码容易理解,异步请求出错时,可以还原完整的调用堆栈,方便调试。

但是Actor之间交互在存在的lua序列化的开销,想避免这个问题,要使用一些天生支持Actor模型的语言,如erlang,或支持并发的语言,如golang,也存在着各自问题。

当一台机器负载不足时,我们需要多台机器,skynet支持的主从策略可以将Actor创建到其他节点上,而逻辑层并不知道这种调整,但它实现的非常简单,当出现节点断开时,异步调用会出现问题,如果解决这些问题,又会增加逻辑开发的复杂度。 针对这问题,skynet提出了另一种集群方案,类似于web服务器,需要调用不同的接口进行跨进程的异步调用。

skynet采用了固定的线程数,以减少线程切换带来的开销,所以actor的数量会远多于线程数,其调度策略是公平的轮询每个actor,但actor在实际中是有不同优先级的。

待补充

RPC调用超时,调用方无法区分

  • 是网络故障还是对方机器崩溃
  • 软件还是硬件错误
  • 是去的路上错误还是回来的路上错误
  • 对方有没有收到请求, 能不能重试

skynet 简单概念

address是什么?

address 可以理解为handle的变量名,有几种格式:

  • :name, name是handle的16进制,一般用于会重复存在的service,如:agent
  • .name, name是本进程唯一的,集群内可以有多个。
  • name, name是集群内唯一的,(后注册的会覆盖前面注册的)。

skynet.launch

开启服务,如果要开启lua服务,可以写为skynet.launch(snlua, lua模块

skynet.newservice

开启lua服务,并在服务退出或出错时,通知创建者。 即等同于:skynet.launch(snlua, lua模块 + 退出回调功能。

skynet.uniqueservice

创建唯一的skynet.newservice, 如果第一个参数为ture,即为创建集群内唯一的服务, 否则是本进程唯一的lua服务

skynet.callskynet.ret

skynet.call配合使用。 需要注意的是:

  1. 只能调用一次,调用两次会出现很难懂的错误日志;
  2. skynet.call不同的是,skynet.ret不支持自动打包;
  3. 可使用封装 skynet.retpack

云风说这两个都是为了兼容老项目而无法改进其封装的形式。

skynet.exitskynet.kill

直接调用exit或者kill都是关闭掉snlua服务,snax.kill的做法是向指定服务发送命令,然后该服务调用服务内的函数后执行skynet.exit,可用类似的方式设计服务器关闭流程。