skynet-轻量级的后台服务器框架源码阅读(一)

一.skynet的安装编译

skynet:在ubuntu16.0.4环境下的安装:

apt-get install git build-essential libreadline-dev autoconf (for ubuntu 16.04)

git clone https://github.com/cloudwu/skynet.git

cd skynet

make linux

 

Make linux出现错误:

error: RPC failed; curl 18 transfer closed with outstanding read data remaining

fatal: The remote end hung up unexpectedly

fatal: early EOF

fatal: index-pack failed

fatal: clone of 'https://github.com/jemalloc/jemalloc.git' into submodule path '3rd/jemalloc' failed

Makefile:41: recipe for target '3rd/jemalloc/autogen.sh' failed

make[1]: *** [3rd/jemalloc/autogen.sh] Error 128

make[1]: Leaving directory '/home/will/myshare/skynet_root/skynet'

platform.mk:40: recipe for target 'linux' failed

make: *** [linux] Error 2

 

error: RPC failed; curl 18 transfer closed with outstanding read data remaining解决方法:

1.缓存区溢出curl的postBuffer的默认值太小,需要增加缓存--使用git命令增大缓存(单位是b,524288000B也就500M左右)

git config --global http.postBuffer 524288000

 

2.修改下载速度--修改下载速度

git config --global http.lowSpeedLimit 0

git config --global http.lowSpeedTime 999999

git config --list

git config --global core.compression 9

 

其他方法:更新远程库到本地

git clone --depth=1 http://xxx.git

git fetch --unshallow

 

针对报错,我的解决方案:cd ./3rd,手动git clone jemalloc源码,再make linux进行编译。

cd ./3rd

git clone https://github.com/jemalloc/jemalloc.git

git clone --depth=1 https://github.com/jemalloc/jemalloc.git

 

二.skynet介绍:

Skynet:是一个轻量级的后台服务器框架;

应用场景:金融,证券,股票,游戏;

 

多核开发:

多核开发解决方案有:

  1,多线程解决方案;

  2,多进程解决方案;

  3,CSP解决方案,由Go语言开发,goroutine协程+channel管道--加强版的多线程解决方案;

  4,还有actor解决方案--也就是加强版的多进程解决方案;

 

实际多线程与语言层面抽象出来的的多线程区别:

实际的多进程/多线程:系统调度多进程。socket多台机器间,多进程间,传递不稳定;

语言层面抽象出来的多进程,由skynet/erlang调度actor。skynet中通过消息(指针)传递,而指针传递的方式是稳定的;推荐的使用方法是在一个进程中解决所有的问题。

 

1,多进程模型,隔离性(运行环境)好,统一性差;(考虑进程间通信的问题)

2,多线程模型,隔离性差,统一性强;(考虑线程同步的问题)。多线程采用锁机制访问临界资源;应用中注意锁粒度;

解决多进程模型中数据一致:常用socket, 共享内存mmap,管道, 信号量

数据一致解决方案有:

1)消息队列:强调的是通知,最终一致性的问题; zeromq推拉模型,请求回应,监听回应,

协议问题;断线重连;进程启动顺序的问题;负载均衡问题(负载均衡:fd%n);数据同步的问题(一致性HASH);

2)数据同步的问题解决方案之Rpc, 强一致性问题,强调处理结果。

3)进程协调管理,Zoomkeeper服务协调的问题(数据模型+监听机制)

 

多个进程竞争有限资源的时候,解决方案:

1,数据中心的进程;

2,n个竞争资源的进程;

操作:

对于竞争资源的进程来说:1,向数据中心请求锁;2,获取锁,进入临界区资源执行相应逻辑;3,释放锁;

对于数据中心的进程来说:1,记录锁,和当前使用的对象;2,主动推送;

 

线程池+队列 与 actor区别:

从控制的角度上看:投递任务到任务队列,简单的负载均衡;适用于针对某一个功能的场景下。

而actor多进程开发中是对进程拆分,1.从功能上考虑;2.热点拆分,比如网关,当到达性能上限时,可以拆分成多个网关协同管理,可以根据逻辑功能做更好的调度,使用更加灵活,受限于CPU和内存。

 

锁机制中不同锁对比:

Spinlock:空转等待,不会发生进程切换,适用于不做复杂逻辑的场景下;

互斥锁:mutex,会发生进程切换,

读写锁:读锁:读状态加锁,共享锁,其他线程以读的模式进行临界区,不会发生堵塞;写锁: 写状态加锁,独享锁,其他线程尝试访问该临界区,都会堵塞等待。

主要适用于读远大于写的场景中;

条件变量:条件不满足的线程会进入睡眠,条件满足会唤醒;常与互斥锁配合使用,具体参

 

这里写线程池伪代码,详细见线程池代码;

 //*****************************************************************

pthread_mutex_lock(&mutex);

while(条件不满足){

  pthread_cont_wait(&cond,&mutex);//释放mutex,cond锁;

}

pthread_mutex_unlock(&mutex);

 //*****************************************************************

pthread_mutex_lock(&mutex);

//修改条件变量状态;

if(条件满足){

  pthread_cont_signal(&cond,&mutex);//唤醒睡眠线程,linux环境下可能会引起虚假唤醒;

}

pthread_mutex_unlock(&mutex);

  //*****************************************************************

pthread_cond_wait存在虚假唤醒的现象:linux环境下,pthread_cond_wait(&cond,&mutex);可能会唤醒多个睡眠的线程;和linux实现相关。

 

三.并发模型:actor,csp

1, actor 是一个并发模型;erlang(进程) ;  skynet  C语言底层实现+lua实现业务逻辑

从语言层面上抽象出进程的概念,选择隔离性,弱化统一性;

A,用于并发计算;b,actor是最基本的计算单元;c,基于消息计算(skynet回调函数,消耗milebox中的消息);d,actor之间相互隔离;

skynet以actor为并发实体

2,csp: goroutine协程;go语言实现的;csp是以goroutine为并发实体;

 

平衡隔离性和统一性;

多进程:隔离性强,统一性差;多进程是以进程为并发实体;

多线程:锁类型,应用,锁粒度; 隔离性差,同一性强;多线程是以线程为并发实体;

skynet以actor为并发实体,skynet核心工作是通过消息调度从而实现对actor的调度。

skynet中用到了锁和线程,每一个消息一个锁,控制锁的粒度比较小;

  worker工作线程轮询全局消息队列,全局消息队列存储的是有消息的actor的消息队列, worker线程从消息队列中取出消息调用回调函数来消费消息;worker线程的数目和机器的CPU核心数是相等的,因此相当于一个计算密集型的线程池。

  定时器线程;

  网络线程;单线程读,多线程写。

 

 

四. skynet中的actor服务介绍

actor适用场景

a)用于并行计算

b)actor是最基本的计算单元

c)基于消息计算-->skynet_cb cb回调函数来执行消息

d)actor之间通过消息沟通并且相互隔离-->隔离的实现机制是内存块+lua虚拟机,消息存储在消息队列中。

 

actor组成:

1,隔离的环境;是一个结构体来实现的,每个actor都有一块独一无二的内存块;

2,回调函数;消费消息队列中的消息;

3,milebox消息队列:用来存储消息;

 

struct skynet_context {
    void * instance;//隔离的环境
    struct skynet_module * mod;
    void * cb_ud;//回调携带的环境
    skynet_cb cb;//回调函数
    struct message_queue *queue;//消息队列
    FILE * logfile;
    uint64_t cpu_cost;  // in microsec
    uint64_t cpu_start; // in microsec
    char result[32];
    uint32_t handle;//actor句柄
    int session_id;
    int ref;
    int message_count;
    bool init;
    bool endless;
    bool profile;

    CHECKCALLING_DECL
};

 

actor隔离环境的实现:分配一块内存,举例在log服务中的源码是这样实现的,

struct logger {
    FILE * handle;
    char * filename;
    int close;
};

struct logger *logger_create(void) {
    struct logger * inst = skynet_malloc(sizeof(*inst));
    inst->handle = NULL;
    inst->close = 0;
    inst->filename = NULL;
    return inst;

}

log服务的功能就是从消息队列中取出消息,然后打印出来。

__init()

__create()

__release();

cb回调函数;消费milebox消息队列中的消息;

 

1] actor运行以及消息调度[如下图所示]actor之间通过milebox消息队列来进行沟通;全局消息队列存储的是有消息的actor的消息队列指针;

       

 

2] actor消息队列存储的是actor的消息;

Work进程逻辑:

  1,取出有消息的actor的消息队列;

  2,取出消息;

  3,通过回调函数(消息)执行;

3] 消息的生产和消费;

  1)消息的产生;actor之间消息的传递;

  2)网络中生产消息;socket从网络中获取到消息数据后,将数据转发到actor进行处理

  3)定时器产生的消息;

消息的消费是通过回调函数来实现的。

 

skynet的启动:

skynet_start.c

    create_thread(&pid[0], thread_monitor, m);//消息过载服务
    create_thread(&pid[1], thread_timer, m);//启动定时器线程
    create_thread(&pid[2], thread_socket, m);//启动一个网络线程

 

五. skynet中的lua服务介绍

lua服务的创建源码如下,

struct snlua {
    lua_State * L;//lua虚拟机,就是lua服务的额隔离环境
    struct skynet_context * ctx;//上下文
    size_t mem;//内存统计
    size_t mem_report;
    size_t mem_limit;
};

skynet中的lua服务通过service_snlua.c来加载的;

lua隔离环境是lua虚拟机;

__init():根据不同的参数生成不同的lua虚拟机

__create()

Skynet_callback()设置skynet中的lua虚拟机

 

End: skynet工作原理总结:

skynet工作原理如下图所示:

             

work线程:轮询全局消息队列,取出消息队列,然后通过回调函数执行消息队列中的消息。

worker线程是如何取消息的,

  通过权重表对所有的worker线程设置权重,(-1是指取一条消息;0表示取全部的消息;1表示取一半的消息);逻辑有<< >>未操作来实现的,weight控制取消息的数量,控制worker线程的公平调度。

回调函数处理完消息后,根据当前actor 消息队列mailbox是否还有消息,如果有消息就将actor消息队列放在全局消息队列的队尾,由其他worker线程继续需处理。在多个worker线程访问全局消息队列这里用到的锁机制是通过自旋锁+条件变量实现的。

 

配置work线程的数目和机器的CPU核心数是相等的;

work线程主要任务执行逻辑,不需要切换,所以这里不适用互斥锁,而使用的是spinlock()

1.work线程从消息队列中取消息,并通过回调函数调用;

2,当没有消息的时候,采用条件变量pthread_cond_wait(&cond,&mutex);进入休眠,释放CPU

 

actor之间发送消息,不需要唤醒worker条件变量;因为worker线程取消息,执行当前actor之间发送的消息,当前至少有一个worker线程是处于活动状态,处理完当前actor消息后接着去继续处理其他actor消息;因此不需要去唤醒新的worker线程去执行消息。

 

posted @ 2020-08-14 00:55  will287248100  阅读(1064)  评论(0编辑  收藏  举报