主页
文章
分类
系列
标签
Skynet
发布于: 2017-2-1   更新于: 2017-2-1   收录于: 框架 , Skynet
文章字数: 1745   阅读时间: 4 分钟  

高性能服务器模型

Reactor 模型

  1. 向事件分发器注册事件回调

  2. 事件发生

  3. 事件分发器调用之前注册的函数

  4. 在回调函数中读取数据,对数据进行后续处理

    Reactor 模型实例:libevent,Redis、ACE

Proactor 模型

  1. 向事件分发器注册事件回调

  2. 事件发生

  3. 操作系统读取数据,并放入应用缓冲区,然后通知事件分发器

  4. 事件分发器调用之前注册的函数

  5. 在回调函数中对数据进行后续处理

    Preactor 模型实例:ASIO

主要区别

  • Reactor:应用在回调函数中读取数据,然后进行后续的数据处理(同步读写操作)
  • Proactor:数据读取由操作系统完成,回调函数只作数据处理(异步读写操作)

与 Reactor 相比,Proactor 显然系统调用更少,因为 Reactor 模式用户进程触发 IO 操作,以等待或者轮询的去查看 IO 操作是否就绪。

适用场景

  • Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序;
  • Proactor:异步接收和同时处理多个服务请求的事件驱动程序;

Actor 模型

计算机 CPU 的计算速度提高(频率的提高)是有限度的,我们能做的是放入多个计算核心。为了利用多核心的计算机,我们需要并发执行。但是多线程的方式会引入很多问题和增加调试难度。我们有个替换的方案,叫做 Actor 模型,Actor 模型是一个通用的并发编程模型,用于处理并发计算。

Actor 模型 = 数据 + 行为 + 消息

模型中一个 Actor 是一个基本的计算单元。它接受消息然后基于接到的消息做一些计算。和面向对象编程有些类似,对象调用(发送消息)另外一个对象(接收到一个消息),基于调用方法(接受到的一个消息)做处理。Actors 一大重要特征在于 Actors 之间相互隔离,它们并不互相共享内存。也就是说,一个 Actor 能维持一个私有的状态,并且这个状态不可能被另一个 Actor 所改变。

Actor 按次序处理消息,比如你发送三个消息给一个 Actor,它们不会被并发处理。如果你想让这三个消息得到并发处理,你需要创建 3 个 Actor,然后分别发送给它们。

Skynet

skynet 综述

skynet 是一个基于事件的高并发消息处理框架。事件主要来源于网络,定时器和信号通知等

skynet 的核心数据结构是 skynet_context,每个 Lua 服务是一个独立的 Lua 虚拟机,这就保证了服务之间的环境隔离,Lua 服务使用协程处理消息,当需要向其他服务通讯时,协程可以挂起等其他服务返回再继续,这让我们一方面能像写同步代码一样“顺序执行”,另一方面当协程挂起时,该服务可以处理其他消息,这就保证了消息的高并发。

服务主要组成部分:

  1. 服务句柄:和进程 ID 类似,用于唯一标识服务(32 位无符号整型,高 8 位代表集群 ID)
  2. 服务模块:模块以动态库的形式提供。在创建 skynet_context 的时候,必须指定模块的名字,skynet 把模块加载进来,创建模块实例,实例向服务注册一个回调函数,用于处理服务的消息。
  3. 消息队列:每个服务都有一个消息队列(二级队列),当队列中有消息时,会主动挂到全局队列(一级队列)。skynet 启动了一定数量的工作线程,不断从全局队列取出消息队列,派发消息给服务的回调函数去处理(一次处理一条二级队列的消息,不一次性处理完二级队列消息,避免其他服务饿死)

注意:服务模块要将数据,通过 socket 发送给客户端时,并不是将数据写入消息队列,而是通过管道从 worker 线程,发送给 socket 线程,并交由 socket 转发。此外,设置定时器也不走消息队列,而是直接将在定时器模块,加入一个 timer_node。其实这样也很好理解,因为 timer 和 socket 线程内运行的模块并不是这里的 context,因此消息队列他们无法消费。

  1. 调用 skynet_mq_push 向消息队列压入一个消息。
  2. 调用 skynet_globalmq_push 把消息队列链到 global_queue 尾部。
  3. 从全局链表弹出一个消息队列,处理队列中的消息,如果队列的消息处理完则不压回全局队列(避免 CPU 空转),如果未处理完则重新压入全局链表,等待下一次处理。

skynet 启动及消息处理:

  1. 第一步初始化各个功能模块,比如句柄,消息队列,模块,定时器,socket 等等。
  2. 然后创建一个 logger 服务。创建一个 bootstrap 服务。
  3. 接着创建一定数量的工作线程,这个数量可由配置指定,工作线程的责任就是派发消息,工作线程的核心逻辑就是调用 skynet_context_message_dispatch 去派发消息,派发完成后,它会进入睡眠状态。
  4. 创建定时器线程,用于记录时间以及实现 timeout 事件;
  5. 创建 sokcet 线程,用于处理 sokcet 消息,socket 和 timeout 事件最终都会转化成消息,交给工作线程派发给服务处理。
  6. 创建 monitor(/ˈmɑːnɪtər/监视器) 线程,这个线程的作用是监控服务有没有出现死循环。

img