曾经我是多么崇尚异步编程,认为异步编程是服务器端编程的通解,事件驱动+消息驱动,设计是那么的优美。刚工作那会,看到这边代码,清一色的使用线程池同步模式,感觉好像进入了石器时代。随着自己慢慢开始负责一些模块的开发,发现其实自己之前的认识有些偏差,异步IO模型不是万金油。
在很多时候,我们需要关心的并不是高并发,而是高吞吐、低延迟。异步IO模型,由于使用事件驱动,事件发生时,需要唤醒某个工作线程,将消息传送给工作者线程,这样唤醒、消息传递无形当中就增加了整个处理流程的延迟。高并发可以实现高吞吐,但是高吞吐并不一定需要高并发。
试想一下一个高磁盘IO的模块,使用异步网络IO模型,磁盘IO的read,write都是阻塞的,磁盘的读写都在10ms以上,一个请求的process如果落到磁盘上基本上都要上百ms。磁盘IO操作阻塞当前线程进而阻塞了其它连接的网络IO。这个时候,即使前端接入的网络IO是异步的又如何呢,系统只有有限的worker线程(通常异步IO模型靠事件驱动在单线程中集成IO和逻辑处理功能),每个worker线程进行的磁盘IO数每秒也是有限的。无法提高真正的效率。另外,内存拷贝也是一大障碍。
事件驱动的异步IO模型,在全内存处理情况下,效率还是很高的;但是一旦逻辑操作有阻塞操作,就会使得吞吐量降下来了。 基于leader/follower 模式的线程池, 响应时间会更好, 尤其是在并发访问比较高的时候。
解决方法:
1. 全异步操作,这是理想情况,需要保证代码中没有阻塞,依赖的库也没有阻塞。
2. 改用leader/follower 模式的线程池,通过提高并发改善响应时间,提高吞吐量。
3. 对于数据呈长尾分布可以使用cache,避免磁盘IO;对于数据呈随机分布的,就不要加cache了,基本上是浪费内存。
方法1是一种理想情况下的解决方案,可以实现以较低的系统负载实现较高的吞吐,但是需要aio支持,尤其是磁盘的aio;方法2简单,较易实现,立竿见影;方法3,在适当的时候可以锦上添花。
Elliptics已经将网络IO和磁盘IO移到线程池中了,采用leader/follower模型,每个线程竞争accept监听socket,进行处理。同样国内的beansdb也采用了同样的方式,可能是出于代码改动小的考虑,依然使用了drive_machine,不过不影响理解。
while(1)
{
if(will_exit)
break;
lock();
accept_fd = accept(listen_fd);
unlock();
process(accept_fd);
}
|
阅读(7325) | 评论(0) | 转发(0) |