半同步半异步(HSHA)模式的服务器模型
2011-12-05 05:47 j.cheen 阅读(2169) 评论(5) 编辑 收藏 举报半年前偶然看到一叫spserver的服务器框架,它将windows下IOCP移植到到libevent,并且以HSHA,LF两种模式实现了服务器框架.我做了点功课,写点心得.
一般来说在设计一个服务器网络框架的时候,需要用到线程池,里面的线程负责执行服务端所有代码.这些代码总的来说可以分为两类:
- 一类负责网络IO部分,也就是从网络读取和发送数据
- 另一类负责处理各种业务逻辑.
通常情况下他们是分离的,网络IO部分不需要管理业务逻辑具体做什么工作,而后者也不关心数据怎么得来,怎么送到网络上.两者就是一个生产者消费者关系.
这里讨论的HSHA就是网络IO部分为异步模式,而业务逻辑部分为同步模式.即:
- 网络IO请求为非阻塞的异步操作,只要调用了,就立即返回,操作结果不用调用方不断的去问:”发送/接收成功了吗?”.网上有一个比较形象的比喻,叫经典的好莱坞法则:你不用给我们打电话,我们会通知你的(you dont call me,i will call you).
- 业务逻辑部分是同步的,虽然不确定服务端线程池中,到底是哪一个线程在执行,但同一时刻只会有一个线程在执行这个网络会话上的业务逻辑.
同步和异步两部分采用一个作业队列(jobQueue)来实现结合,一些细节:
- 服务端定义一组回调函数,用于将网络连接上的OnAccept,OnRecv,OnSend,OnClose等事件通知业务逻辑层,而业务逻辑层的实现代码正是在这些回调函数中实现.
- 网络连接建立时,服务端会创建一个网络会话对象,并保存有相关回调函数的指针.将回调函数OnAccept和相关上下文打包成一个任务push到作业队列.
- 网络连接收到数据后,服务端将回调函数OnRecv和相关上下文打包成一个任务push到作业队列.
- 网络连接发送数据后,服务端将回调函数OnSend和相关上下文打包成一个任务push到作业队列.
- 网络连接关闭后,服务端将回调函数OnClose和相关上下文打包成一个任务push到作业队列.
- 负责执行作业的某个线程被唤醒,取出这些作业,并执行.
以下代码说明大概的处理流程:
1: class job_queue;
2: class msg_free_queue;
3: class thread_pool;
4: int main()
5: {
6: job_queue jobs;
7: msg_free_queue msg_to_free;
8: thread_pool job_thread;
9: thread_pool clean_thread;
10: server_init_and_listen();
11: run_accept_thread();
12: while(!exit_falg){
13: run_iocp_event_loop(&jobs,&msg_to_free);
14: while(job_queue.size() > 0){
15: job_thread.push(job_queue.pop_front());
16: }
17: while(msg_to_free.size() > 0){
18: clean_thread.push(msg_to_free.pop_front());
19: }
20: };
21: }
下面是一些题外话:
你也看到了,只有一个线程负责处理IO部分,那么性能如何呢?
这个吧,其实大多数时候,你的服务器性能瓶颈不再网络IO部分,而在业务逻辑处理部分,在那里可能产生磁盘IO,数据库操作等.我个人觉得但线程IO已经足够了.采用IOCP这样高效的IO模型是为业务逻辑处理腾出资源,因为一个高效的服务端不是看你每秒能收发多少数据,而是看业务处理能力.另外单线程的IOCP也有很多好处,首先就是锁的问题解决了,其次就是资源的管理简单了,这两个问题都是比较棘手的东西.
我编译了spserver的代码,用asio的乒乓测试客户端程序做了一下压力测试,结果你可能也猜到了,IO吞吐量并不算很高,而且多核CPU上,只有一个CPU达到了100%利用率,而另外几个CPU则利用率不足50%.如果你跟我一样,是一个完美主义者,我们可以这样改进它:
将上面的代码中,监听工作和Accept线程分离出来,主循环那的部分(while那一块和相关的上下文环境)封装成一个模块,每一个模块都有自己完成端口句柄,当连接建立时,做一个简单的负载均衡,为新建立的连接的套接字句柄选择一个完成端口句柄并绑定.于是就变成了下面的样子:
1: class job_queue;
2: class msg_free_queue;
3: class thread_pool;
4: int nod();
5: int main()
6: {
7: thread_pool io_thread_nods;
8: io_thread_nods.set_thread_count(4);
9: server_init_and_listen();
10: run_accept_thread();
11: while(true){
12: io_thread_nods.run(nod);
13: }
14: }
15: int nod()
16: {
17: job_queue jobs;
18: msg_free_queue msg_to_free;
19: thread_pool job_thread;
20: thread_pool clean_thread;
21: while(!exit_falg){
22: run_iocp_event_loop(&jobs,&msg_to_free);
23: while(job_queue.size() > 0){
24: job_thread.push(job_queue.pop_front());
25: }
26: while(msg_to_free.size() > 0){
27: clean_thread.push(msg_to_free.pop_front());
28: }
29: };
30: }
OK,完工.