免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 10352 | 回复: 16
打印 上一主题 下一主题

[C] 两种并发服务器的设计模式比较 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-04-23 21:58 |只看该作者 |倒序浏览
《unix 网络编程》中一章讲并发服务器的设计模式,在第二版中作者给出了各种设计模式的例子及其测试数据。对于他给出的例子,处理时间最短的是这样的模式:
      模式1
      (都是单进程的)一个进程中有预先创建多个线程都阻塞在accept函数(或者调用这个函数之前的pthread_mutex_lock函数)上,也就是有多个监听线程,任何一个监听线程从accept返回得到一个socket就自己处理这个socket.
      还有一种模式,是这样的:
      模式2
      预先创建多个线程,不过只有一个线程阻塞在accept即只有一个监听线程,这个线程得到socket以后把这个socket给一个空闲线程,用条件变量通知那个空闲进程。
      并且分析了为什么前一种模式的处理时间短,因为第一种模式中线程在调用accept之前只要获得锁,而在后一种模式中不仅需要锁,还要条件变量。

     最近找了一些并发服务器的代码,似乎大家采用的模式跟第二种差不多:
     模式2a
     只有一个监听线程阻塞在accept上,得到socket以后向线程池申请一个线程(如果有空闲的就选一个,否则就新创建一个线程),并且使用epoll轮训那个socket可读或是有新连接。
     这种模式的好处是可以在启动比较少的线程的情况下,接受上万个连接socket。

      我觉得第一种模式也可以改成这样的方式:
     模式1a
      每个监听线程得到一个连接socket以后,检查一下有没有监听线程阻塞在accept上(或者之前的pthread_mutex_lock),也看看是否有其他线程在监听,如果没有就新创建一个监听线程进行监听,然后当前线程处理刚才接受的socket。  至于主线程可以检查阻塞的线程是否过多,如果过多就杀死一部分。
      由于一个进程的线程是有数的,大概几百个这样(我用的32位linux),实际上同时接受的连接就比较少了。
      
      至于《unp》中的例子,模式1处理时间短,那是因为起的线程比较少,最多的测试用例才起了个几十。

论坛徽章:
0
2 [报告]
发表于 2008-04-24 08:59 |只看该作者
epoll

论坛徽章:
0
3 [报告]
发表于 2008-04-24 09:18 |只看该作者
epoll是肯定要用的。

模式1a还可以改一下,每一个线程都使用epoll服务多个客户端的链接,同时也用epoll轮训listen返回来的监听socket。这种模式与模式2的差别就很小了,只是这种模式多个线程监听,而且不把accept的链接socket传给其他线程处理而是自己处理。
请问有人用过这中模式吗?

论坛徽章:
0
4 [报告]
发表于 2008-04-24 09:47 |只看该作者
原帖由 UnixStudier 于 2008-4-24 09:18 发表
epoll是肯定要用的。

模式1a还可以改一下,每一个线程都使用epoll服务多个客户端的链接,同时也用epoll轮训listen返回来的监听socket。这种模式与模式2的差别就很小了,只是这种模式多个线程监听,而且不把a ...


相比而言,我更愿意用这样的方式来分类
1.单线程 event-driven,最典型的例子就是 memcached
2.每个连接一个线程,这是一种比较常见的模式,在顶楼提到的两种模式都是属于这种
3.多线程 event-driven,使用 半同步半异步线程池,或者 leader/follower 线程池

如果希望线程数和连接数不会直接相关,那么不能使用第二种,而是要使用第一种或者第三种。
1 和 3 的区别在于具体的业务处理过程(除掉和 client 的 socket 读写时间)中会不会需要很长的时间。
类似 memcached 这种,业务过程主要是在内存操作,不需要很长的时间,因此可以使用单线程。
如果类似于 im 这种,比如实现一个用户登录功能,这个功能需要访问数据库,那么使用单线程就有风险。
万一数据库的响应很慢,就会导致某一个用户的登录请求会导致整个服务器都挂在和数据库的连接上。

论坛徽章:
0
5 [报告]
发表于 2008-04-24 09:53 |只看该作者
原帖由 UnixStudier 于 2008-4-23 21:58 发表
《unix 网络编程》中一章讲并发服务器的设计模式,在第二版中作者给出了各种设计模式的例子及其测试数据。对于他给出的例子,处理时间最短的是这样的模式:
      模式1
      (都是单进程的)一个进程中有预 ...



由于目前的server 机器大多是双核或者多核, 而在支持smp的os上, 我认为最合适的处理长连接的架构是

多进程accept + async io , 当然异步io实现方法有很多, 例如(kqueue, epoll),  这样可以朵开不必要的lock跟thread 调度

如果是处理大量短连接, 可考虑apache 的架构。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
6 [报告]
发表于 2008-04-24 10:39 |只看该作者
模式2a
     只有一个监听线程阻塞在accept上,得到socket以后向线程池申请一个线程(如果有空闲的就选一个,否则就新创建一个线程),并且使用epoll轮训那个socket可读或是有新连接。

这种模式.."得到socket之后" 将这个sokcet推给哪个线程了? 临时的线程还是单独利用epoll来轮循所有已经建立连接的socket的线程?

感觉说的有点儿模糊.

论坛徽章:
0
7 [报告]
发表于 2008-04-24 13:20 |只看该作者
原帖由 UnixStudier 于 2008-4-23 21:58 发表
《unix 网络编程》中一章讲并发服务器的设计模式,在第二版中作者给出了各种设计模式的例子及其测试数据。对于他给出的例子,处理时间最短的是这样的模式:
      模式1
      (都是单进程的)一个进程中有预先创建多个线程都阻塞在accept函数(或者调用这个函数之前的pthread_mutex_lock函数)上,也就是有多个监听线程,任何一个监听线程从accept返回得到一个socket就自己处理这个socket.


这种按 ACE 的说法,是  在 accept 阶段使用 leader/follower 线程池模式。

      还有一种模式,是这样的:
      模式2
      预先创建多个线程,不过只有一个线程阻塞在accept即只有一个监听线程,这个线程得到socket以后把这个socket给一个空闲线程,用条件变量通知那个空闲进程。
      并且分析了为什么前一种模式的处理时间短,因为第一种模式中线程在调用accept之前只要获得锁,而在后一种模式中不仅需要锁,还要条件变量。


这种按 ACE 的说法,可以算是 在 accept 阶段使用 半同步半异步 方式。比起 leader/follower 模式,需要较多的线程切换消耗。

     最近找了一些并发服务器的代码,似乎大家采用的模式跟第二种差不多:
     模式2a
     只有一个监听线程阻塞在accept上,得到socket以后向线程池申请一个线程(如果有空闲的就选一个,否则就新创建一个线程),并且使用epoll轮训那个socket可读或是有新连接。
     这种模式的好处是可以在启动比较少的线程的情况下,接受上万个连接socket。


这里的描述不够清晰。 猜测应该是这样:accept 之后,应该并不是立即申请线程,把 socket 和线程绑定。应该是先把 socket 加入到 epoll 的轮询集合中,直到某个 socket 可读,然后才申请线程进行处理。如果是这样,那么按 ACE 的说法,是在 读写阶段使用 半同步半异步 模式。

      我觉得第一种模式也可以改成这样的方式:
     模式1a
      每个监听线程得到一个连接socket以后,检查一下有没有监听线程阻塞在accept上(或者之前的pthread_mutex_lock),也看看是否有其他线程在监听,如果没有就新创建一个监听线程进行监听,然后当前线程处理刚才接受的socket。  至于主线程可以检查阻塞的线程是否过多,如果过多就杀死一部分。
      由于一个进程的线程是有数的,大概几百个这样(我用的32位linux),实际上同时接受的连接就比较少了。
      
      至于《unp》中的例子,模式1处理时间短,那是因为起的线程比较少,最多的测试用例才起了个几十。


在这种模式中,主线程除了 检查线程是否过多 ,还有其他的职责吗?

在上述列举的 1,2,1a 这 三种模式中,在读写阶段连接和线程都是绑定的。在这种情况下,连接数是受线程数限制的。
对于 2a ,如果我的猜测是对的话,那么这种在读写阶段使用 半同步半异步的 模式,就没有对链接和线程进行绑定,因此连接数不受线程数的限制。

[ 本帖最后由 iunknown 于 2008-4-24 13:25 编辑 ]

论坛徽章:
0
8 [报告]
发表于 2008-04-26 07:20 |只看该作者
模式2a 我说的不太清楚,就是unknown 上面猜测的。

论坛徽章:
0
9 [报告]
发表于 2008-04-26 07:22 |只看该作者
原帖由 xhl 于 2008-4-24 09:53 发表



由于目前的server 机器大多是双核或者多核, 而在支持smp的os上, 我认为最合适的处理长连接的架构是

多进程accept + async io , 当然异步io实现方法有很多, 例如(kqueue, epoll),  这样可以朵开不必 ...


听说现在的异步io实现得都不怎么好。正在查找相关资料。
至于“多进程accept”,只要服务器程序支持产生多个实例就行。每个实例内部还是多线程的。
可以参考apache httpd。

论坛徽章:
0
10 [报告]
发表于 2008-04-26 20:28 |只看该作者
好贴!
对开发高性能网络服务器很有帮助。

我也曾实现过一个并发服务器的框架,ab和webbench工具测试结果相当的好。
具体实现是这样子:
主进程accept,得到socket后按某种策略分发到处理子进程,
子进程得到socket后将其加入读线程epoll进行读监听,子进程主线程继续监听下一个socket,
epoll读到某个socket的有效数据包后交由处理线程池中的空闲线程进程业务逻辑处理,
处理线程返回客户端响应包则通过写线程的epoll进行处理。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP