340 likes | 596 Views
Erlang 进程模型在 C++ 中的实践. ac_actor 实现浅析. 实际案例. 设计需求. 1. 开发一个取用户基础信息及好友列表的访问服务器,客户端通过访问此服务器便能获取到某个用户的信息及好友列表。 2. 用户基础信息和好友信息存放在其他机器上,访问服务器只能通过不同的接口获取这两类信息。 3. 访问服务器需要记录每个客户端的 ip 地址。 4. 请求的平均处理时间尽可能的短. 访问请求流程图. 找出流程中可并行的部分. 基于 Callback+ 状态机实现的优缺点. 优点 常规方法,原理容易理解 缺点
E N D
Erlang进程模型在C++中的实践 ac_actor实现浅析 51.com系统架构组
实际案例 51.com系统架构组
设计需求 1. 开发一个取用户基础信息及好友列表的访问服务器,客户端通过访问此服务器便能获取到某个用户的信息及好友列表。 2. 用户基础信息和好友信息存放在其他机器上,访问服务器只能通过不同的接口获取这两类信息。 3. 访问服务器需要记录每个客户端的ip地址。 4. 请求的平均处理时间尽可能的短 51.com系统架构组
访问请求流程图 51.com系统架构组
找出流程中可并行的部分 51.com系统架构组
基于Callback+状态机实现的优缺点 • 优点 • 常规方法,原理容易理解 • 缺点 • 代码编写较复杂,程序员大部分的精力都放在了如何更好的构造和处理状态机上 • 后期维护,状态扩展较复杂 51.com系统架构组
基于ac_actor实现的伪码 void process_request() { Future getUserInfo(&get_user_info); // launch step 2 Future getFriendList(&get_friend_list); // launch step 3 Future logClientIp(&log_client_ip); // launch step 4 // coroutine will be blocked until the two futures are finished if ( getUserInfo.finished() && getFriendList.finished() ) { send(client, resultMsg); // step 5 } logClientIp.finished(); //wait for logging finished } 51.com系统架构组
基于ac_actor实现的优缺点 • 优点 • 用同步的方法编写异步程序,简单高效 • 代码清晰,逻辑简单 • 缺点 • 需要熟悉相应接口 51.com系统架构组
ac_actor实现剖析 51.com系统架构组
Erlang进程模型特点 • 一个进程可以创建数以万计的轻量级进程 • 每个轻量级进程仅仅完成单一功能 • 一个复杂的功能可以由多个轻量级进程协同完成 • 当遇到file或socket等阻塞的io时, 调用的轻量级进程被阻塞,整个进程不阻塞 51.com系统架构组
设计目标 • 借鉴ErLang的轻量级进程程模型,设计出一套C++的底层开发库,使上层C++程序可以轻松的用同步语法写出异步程序 • 实现轻量级进程的创建,销毁,以及进程间的切换 • 支持轻量级进程间的消息通信 • 支持Socket IO,文件IO,计时器(sleep) 等常用API 51.com系统架构组
设计思路 • 如何轻量级进程? coroutine • 在轻量级进程的基础上实现出process和scheduler两种角色 • process执行用户函数,当调用到socket io, file io, sleep, receive等阻塞操作时,process先注册事件,之后主动把执行权交给scheduler执行 • scheduler基于事件模型,当有io或定时器等事件完成时,scheduler找到相应的process,并将执行权转交给该process,process继续执行剩余代码 51.com系统架构组
Coroutine (协程) var q := new queue // 消息队列 coroutine produceloopwhile q is not full create some new items add the items to qyield to consume // 主动将执行权切换到 consume 协程coroutine consumeloopwhile q is not empty remove some items from q use the itemsyield to produce // 主动将执行权切换到 produce 协程 51.com系统架构组
Coroutine的C实现库 • Libcoro • coro_create (coro_context *ctx, coro_func coro,// 函数入口 void *arg, // 函数参数 void *stackPointer, // 用户指定运行栈 long stackSize); // 栈大小 • coro_transfer (coro_context *prev, // 切出的coroutine coro_context *next);// 切入的coroutine • coro_destroy (coro_context *ctx); • libcoroutine 51.com系统架构组
基于coroutine的轻量级进程 • 与传统线程相比,coroutine创建销毁速度非常快 • coroutine基于ucontext的实现在普通的笔记本上能达到200w次/秒的切换 • coroutine的栈大小可以在创建时指定,在内存16G的64位机器上,理论上一个进程可以同时拥有4M个栈大小为4K的coroutine • 缺点 • coroutine的栈大小一般为4K,程序不能在栈上分配过多的资源 51.com系统架构组
Process内部结构 51.com系统架构组
Scheduler内部结构 51.com系统架构组
Process 的创建和通信 • 创建原语 spawn (user_func, args) spawn_with_stack (stack_size, user_func, args) • 进程调度/切换原语 yield(); yield_and_schedule(); • 消息处理原语 send (pid, send_message) message * receive (timeout) reply (received_message, send_message) 51.com系统架构组
图例 - 初始状态 51.com系统架构组
图例 - spawn process 51.com系统架构组
图例 - send & reply 51.com系统架构组
图例 - process die 51.com系统架构组
常用API的实现 51.com系统架构组
Socket IO API 实现 • 接口 • socket_accept • socket_connect • socket_read • socket_write • 思路 • 调用时先注册事件,并交出执行权,以免整个进程阻塞 • scheduler进入事件循环 • 当相应事件到达时,scheduler将执行权交给相应process • 此时process执行 accept/connect/read/write等操作时不会挂起整个进程 51.com系统架构组
socket_read 实现原理 51.com系统架构组
File IO API 实现 • 接口 • file_read • file_write • open • stat • close ... • 思路 • file_read,file_write 可以利用aio和scheduler的整合来实现,aio的调用不会阻塞整个进程 • open,stat等文件操作会阻塞整个进程,且没有相应的aio,其fd也不能注册事件,因此必须另想办法。。。 51.com系统架构组
file_read 实现原理 51.com系统架构组
线程池的引入 • 遗留问题 • 如果用户在process中调用open,stat等文件操作会阻塞整个进程 • 如果用户在process中执行复杂的耗cpu的操作,process不主动交出执行权,则scheduler没有机会执行,其他process陷入饥饿状态 • 解决方法 • 引入线程池,将 阻塞的系统api和耗时的运算 都放到线程池中执行 51.com系统架构组
file_open实现 51.com系统架构组
案例 回顾 Future对象如何实现? 51.com系统架构组
案例 图示 51.com系统架构组
已实现的特性 • process的创建,切换与销毁 • spawn / yield • process间的消息通信 • send / receive / reply • Socket/File IO实现 • 程序切入到线程中执行 • switch_to_threadpool/ switch_to_scheduler • 分布式通信框架 51.com系统架构组
今后会加入的特性 • 引入fastfail机制 • 实现SMP版本 51.com系统架构组
Q & A 51.com系统架构组