一.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线程去执行消息。