Skynet 设计综述

  1. 多线程模式,可使得状态共享、数据交换更加高效。而多线程模型的诸多弊端,好比复杂的线程锁、线程调度问题等,均可以经过减少底层的规模,精简设计,最终把危害限制在很小的范围内。
  2. 作为核心功能,Skynet 仅解决一个问题:把一个符合规范的 C 模块,从动态库(so 文件)中启动起来,绑定一个永不重复(即便模块退出)的数字 id 作为其 handle 。模块被称为服务(Service),服务间能够自由发送消息。每一个模块能够向 Skynet 框架注册一个 callback 函数,用来接收发给它的消息。每一个服务都是被一个个消息包驱动,当没有包到来的时候,它们就会处于挂起状态,对 CPU 资源零消耗。若是须要自主逻辑,则能够利用 Skynet 系统提供的 timeout 消息,按期触发。Skynet 提供了名字服务,还能够给特定的服务起一个易读的名字,而不是用 id 来指代它。id 和运行时态相关,没法保证每次启动服务,都有一致的 id ,但名字能够。
  3. Skynet 原则上主张全部的服务都在同一个 OS 进程中协做完成。因此在核心层内,不考虑跨机通信的机制,也不为单独一个服务的崩溃,重启等提供相应的支持。
  4. Skynet 只负责把一个数据包从一个服务内发送出去,让同一进程内的另外一个服务收到,调用对应的 callback 函数处理。
  5. 数据包一般是在一个服务内打包生成的,Skynet 并不关心数据包是怎样被打包的,它甚至不要求这个数据包内的数据是连续的(虽然这样很危险,在后面会谈及的跨机通信中会出错,除非你保证你的数据包绝对不被传递出当前所在的进程)。它仅仅是把数据包的指针,以及你声称的数据包长度(并不必定是真实长度)传递出去。因为服务都是在同一个进程内,接收方取得这个指针后,就能够直接处理其引用的数据了。api

    这个机制能够在必要时,保证绝对的零拷贝,几乎等价于在同一线程内作一次函数调用的开销。session

    但,这只是 Skynet 提供的性能上的可能性。它推荐的是一种更可靠,性能略低的方案:它约定,每一个服务发送出去的包都是复制到用 malloc 分配出来的连续内存。接收方在处理完这个数据块(在处理的 callback 函数调用完毕)后,会默认调用 free 函数释放掉所占的内存。即,发送方申请内存,接收方释放。多线程

  6. 咱们来看看 skynet_send 和 callback 函数的定义:框架

    int skynet_send(
      struct skynet_context * context, 
      uint32_t source, 
      uint32_t destination,
      int type,
      int session,
      void * msg, 
      size_t sz
    );
    
    typedef int (*skynet_cb)(
      struct skynet_context * context,
      void *ud, 
      int type, 
      int session, 
      uint32_t source ,
      const void * msg,
      size_t sz
    );
    
    //发送一个数据包,就是发送 msg/sz 对。
    //咱们能够在 type 里打上 dontcopy 的 tag (PTYPE_TAG_DONTCOPY) ,让框架不要复制 msg/sz 指代的数据包。不然 skynet 会用 malloc 分配一块内存,把数据复制进去。callback 函数在处理完这块数据后,会调用 free 释放内存。你能够经过让 callback 返回 1 ,阻止框架释放内存。这一般和在 send 时标记 dontcopy 标记配对使用。
  7. skynet 核心并不解决进程间通信的问题。数据交换方式并不是相似 TCP 的数据流,因此,没有必要把服务间的通信形式强行统一为单个数据块。最合适作进程内通信的方式就是 C 结构。消息发送方和接收方都处于同一个进程内时,它们必定能够识别同一个 C 结构映射的内存块,没必要考虑内存布局,字节序等问题。在这个层面上使用这种更高效的数据交换方式,能够极大的提高性能。tcp

  8. session 是什么?因为每一个服务仅有一个 callback 函数,比如在 ip 协议中去掉了端口的设定,全部发送到一个 ip 地址上的 ip 包就没法被分发到不一样的进程了。这时,咱们就须要有另外一个东西来区分这个包。这就是 session 的做用。使用 skynet_send 发送一个包的时候,你能够在 type 里设上 alloc session 的 tag (PTYPE_TAG_ALLOCSESSION)。send api 就会忽略掉传入的 session 参数,而会分配出一个当前服务历来没有使用过的 session 号,发送出去。同时约定,接收方在处理完这个消息后,把这个 session 原样发送回来。这样,编写服务的人只须要在 callback 函数里记录下全部待返回的 session 表,就能够在收到每一个消息后,正确的调用对应的处理函数。函数

  9. type 的做用?type 表示的是当前消息包的协议组别,而不是传统意义上的消息类别编号。协议组别类型并不会不少,因此,我限制了 type 的范围是 0 到 255 ,由一个字节标识。在实现时,我把 type 编码到了 size 参数的高 8 位。由于单个消息包限制长度在 16 M (24 bit)内,是个合理的限制。这样,为每一个消息增长了 type 字段,并无额外增长内存上的开销。布局

  10. 为何整个系统不统一使用一种消息编码协议?这样,全部服务间都不会有沟通障碍。这样,甚至 session 参数也能够编码在数据包中。那仅仅是一丁点效率问题。可是,在真正的项目开发中,这其实作起来很难。尤为在使用第三方库的时候,你须要作不少低效的封装工做,才能够把交互协议都统一块儿来。并且,这个一致的编码协议也就成了系统底层约定的一部分,成为全部开发人员都须要了解的知识性能

  11. handle 也就是每一个服务的地址,在接口上看用的是一个 32 位整数。但实际上单个服务中 handle 的最终限制在 24bit 内,也就是 16M 个。高 8 位是保留给集群间通信用的。咱们最终容许 255 个 skynet 节点部署在不一样的机器上协做。每一个 skynet 节点有不一样的 id 。这里被称为 harbor id 。这个是独立指定,人为管理分配的(也能够写一个中央服务协调分配)。每一个消息包产生的时候,skynet 框架会把本身的 harbor id 编码到源地址的高 8 位。这样,系统内全部的服务模块(用handle来标识,高8位是 harbor id ,低24为本节点中的服务标识符),都有不一样的地址了。从数字地址,能够轻易识别出,这个消息是远程消息,仍是本地消息。ui

  12. 集群间的通信,是由一个独立的 harbor 服务来完成的。全部的消息包在发送时,skynet 识别出这是一个远程消息包时,都会把它转发到 harbor 服务内。harbor 服务会创建 tcp 链接到全部它认识的其它 skynet 节点内的 harbor 服务上。编码

  13. 全局名字服务:

    skynet 目前支持一个全局名字服务,能够把一个消息包发送到特定名字的服务上。这个服务没必要存在于当前 skynet 节点中。这样,咱们就须要一个机构可以同步这些全局名字。为此,我实现了一个叫作 master 的服务。它的做用就是广播同步全部的全局名字,以及加入进来的 skynet 节点的地址。本质上,这些地址也是一种名字。一样能够用 key-value 的形式储存。即,每一个 skynet 节点号对应一个字符串的地址。

  14. 组播

相关文章
相关标签/搜索