skynet 分布式,skynet性能
服务器高层架构-冯云天网
用天网之手撕碎一个万人网游。
天网是我们游戏服务器的底层框架。我在选技术的时候仔细看了它的源代码,发现它是C语言的工程模型。大部分游戏服务器要么用C,要么用java,用C的非常少,但是天网通过C和Lua的结合,实现了一个高效的游戏框架。C层没有冗余的三方库,而是紧凑的核心结构,提供了核心的消息处理框架。Lua层用来写游戏逻辑,降低了开发门槛。
目前天网广泛应用于阿里游戏。据我所知,热门三国志用的是天网,我们的游戏当然也用这个框架,已经稳定运行一年多了。
说到天网,不能算是游戏服务器框架。它只是为游戏服务器提供一些必要的基础设施,可以用来设计符合要求的上层逻辑。根据冯云的说法,天网实现了类似于Erlang的Actor模型,它本质上是一个高度并发的消息处理框架。消息从底层分发到上层“服务”进行处理。这里的服务可以用C写,当然大部分时候是用Lua写的,每个Lua服务都是一个独立的Lua虚拟机,保证了服务之间的环境隔离。Lua服务使用协同流程来处理消息。当它需要与其他服务通信时,协同过程可以暂停,直到其他服务返回,然后继续。这一方面让我们可以像写同步代码一样“顺序执行”,当协同学进程暂停时,服务可以处理其他消息,保证了消息的高并发。
由于天网内核的精简,很多人抱着开箱即用的想法。后来发现门槛其实不低。还是需要你熟悉游戏服务器业务,知道自己想要达到的目标,然后自己去做。但正是因为它的简化,才具有高度的可定制性。
天网的核心功能如果要用一句话来形容天网的核心功能是什么:依然是基于事件的高并发消息处理框架。事件主要来自网络、定时器、信号通知等。当事件被触发时,天网将这些事件编码成消息结构,发送给感兴趣的服务进行处理;当一个服务处理一个消息时,它也可以主动向其他服务发送一个消息。所以,他是事件驱动的,没有前述事件,天网什么都做不了。
天网的核心数据结构是skynet_context。我对Erlang不熟悉,分不清它对应的是什么结构。但它实际上也像操作系统中进程的概念。在这里我们称之为服务。服务包含以下内容:
服务句柄:类似于进程ID,用于唯一标识服务。
服务:模块以动态库的形式提供。创建skynet_context时,必须指定模块的名称。天网加载模块并创建模块的一个实例。实例向服务注册一个回调函数来处理服务的消息。
消息队列:每个服务都有一个消息队列。当队列中有消息时,它将被主动链接到全局链表。天网启动一定数量的工作线程,不断从全局链表中取出消息队列,将消息发送给服务的回调函数进行处理。
下面的结构图显示了天网的核心结构:
Handles每个服务都与一个句柄相关联,这个句柄在Skynet _ handle.h C中实现,句柄是一个32位无符号整数,最高的8位代表集群ID(已弃用),剩余的24位是服务ID。
Handle_storage用于存储ID和skynet_context的映射:
//句柄存储结构
结构句柄_存储{
struct rwlock锁;//读写锁
uint32_t港;//群集ID
uint32 _ t handle _ index//当前句柄索引
int slot _ size//插槽数组大小
struct skynet_context **槽;//skynet_context数组
… …
};
服务模块让我们先来看看创建服务的API:
//创建服务:name是服务模块的名称,parm是参数,模块自己解释含义。
struct skynet _ context * skynet _ context _ new(const char * name,const char * parm);
这里的name参数是模块名。天网根据这个名字加载模块,调用约定的导出函数。这个过程大概是这样的:
获得模块后,调用skynet_module_instance_create函数创建模块实例。
然后调用skynet_module_instance_init来初始化实例。通常情况下,实例在初始化时调用skynet_callback向skynet设置一个回调函数,以后的消息处理将由这个回调函数处理。
当消息队列创建服务时,也会创建一个新的消息队列。消息队列在skynet_mq.ch中实现,它由以下结构表示:
//消息队列
结构消息队列{
struct spinlock锁;
uint32_t句柄;//关联的服务句柄
int cap//队列容量
int head//队列头的位置
int tail//队列末尾的位置
结构skynet _ message * queue//消息结构的数组
struct message _ queue * next//指向下一个消息队列
… …
};
Next指向下一个消息队列,也就是说message_queue会形成一个链表,然后由global_queue保存,是这样的:
结构全局队列{
struct message _ queue * head
struct message _ queue * tail
struct spinlock锁;
};
global_queue持有的链表就是需要处理消息的消息队列。这个过程如下:
调用skynet_mq_push将消息推入消息队列。
然后调用skynet_globalmq_push将消息队列链接到global_queue的末尾。
从全局链表中弹出一个消息队列来处理队列中的消息。如果队列中的消息被处理,它们将不会被推回到全局链表中。如果不处理,就推回全局链表,等待下一次处理。
描述比较简单,具体细节还是要查函数Skynet _ context _ message _ dispatch。
天网启动和信息处理。在介绍了该服务的三个重要组件之后,您现在可以看看skynet_context的内容:
结构skynet_context {
void *实例;//服务模块的实例指针
struct skynet _ module * mod//服务模块指针
void * cb _ ud//回调函数的用户数据
天网_ cb cb//业务处理消息的回调函数
struct message _ queue * queue//消息队列
uint32_t句柄;//服务句柄
… …
};
其实最核心的部分就是上面说的三个,那么天网是如何启动并不断处理消息的呢?答案是天网_start这个函数:
第一步是初始化各个功能模块,比如句柄、消息队列、模块、定时器、套接字等等。
然后创建一个日志服务。创建引导服务。
然后创建一定数量的工作线程,可以通过配置指定,工作线程的职责是分发消息。
创建定时器线程记录时间,实现超时事件;
创建一个sokcet线程来处理sokcet消息。套接字和超时事件最终将被转换为消息,这些消息将被发送到工作线程进行服务处理。
创建一个监控线程,用来监控服务中是否存在无限循环。
如前所述,天网由事件驱动。这里主要有两个事件,一个是socket,一个是timeout。分别由两个线程运行。
工作线程的核心逻辑是调用Skynet _ context _ message _ dispatch来调度消息。分派之后,它会进入睡眠状态,等待另外两个线程醒来。这是非常典型的生产消费模式。这是大多数服务器程序的核心功能,天网也不例外:
勿忘你的倡议心灵雅原创作品,博主,
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。