范德萨发而为
全部博文(392)
分类:
2010-09-18 23:24:35
一.C10K的问题 C10K的问题在上个世纪90年代就被提出来了。大概的意思是当用户数超过1万时,很多设计不良好的网络服务程序性能都将急剧下降、甚至瘫痪。并且,这个问题并不能通过升级硬件设备解决,是操作系统固有的问题,也就是说,如果你的服务器最高能支撑1000个并发,尽管你升级了计算能力高一倍的 cpu,内存再翻一番,硬盘转速在快一倍,也无法支撑2000个并发。 经典的网络编程模型有4个: 1. Serve one client with each thread/process, and use blocking I/O。即对每个客户都使用不同的线程或进程进行服务,在每个线程或进程中使用阻塞I/O。这是小程序和java常用的策略,对于交互式的应用也是常见的选择,这种策略很能难满足高性能程序的需求,好处是实现极其简单,容易实现复杂的交互逻辑。我们常用的Apache、ftpd等都是这种工作。 2. Serve many clients with single thread, and use nonblocking I/O and readiness notification。即对所有的客户使用单一一个线程或进程进行服务,在这个线程或进程里,采用异步IO的策略。这是经典模型,优点在于实现较简单,方便移植,也能提供足够的性能;缺点在于无法充分利用多CPU的资源。 3. Serve many clients with each thread, and use nonblocking I/O and readiness notification 对经典模型2的简单改进,仍然采用异步IO的策略,但对所有的客户使用多个线程或进程进行服务。缺点是容易在多线程并发上出bug,甚至某些OS不支持多线程进行readiness notification 4. Serve many clients with each thread, and use asynchronous I/O 在有AI/O支持的OS上,能提供相当高的性能。不过AI/O编程模型和经典模型差别相当大,基本上很难写出一个框架同时支持AI/O和经典模型。这个模型主要是用于window平台上。 在linux上开发高性能的网络应用,只能选着第2、3种方式。考虑到复杂性,我们往往只采用第2种。下面就讨论一下第二种模型。 我们知道,实现异步IO一般是采用select 或poll来实现。Select 定义如下: int select(int n, fd_set *rd_fds, fd_set *wr_fds, fd_set *ex_fds, struct timeval *timeout);Poll 的接口如下: int poll(struct pollfd *ufds, unsigned int nfds, int timeout); 然而 Select 和Poll 在连接数增加时,性能急剧下降。这有两方面的原因:首先操作系统面对每次的select/poll 操作,都需要重新建立一个当前线程的关心事件列表,并把线程挂在这个复杂的等待队列上,这是相当耗时的。其次,应用软件在select/poll 返回后也需要对传入的句柄列表做一次扫描来判断哪些句柄是可用的,这也是很耗时的。这两件事都是和并发数相关,而I/O 事件的密度也和并发数相关,导致CPU 占用率和并发数近似成O(n2)的关系。 因为以上的原因,Unix 上开发了性能更高的epoll, kqueue, /dev/poll 这3个程序接口来解决上述问题。其中epoll 是linux 的方案,kqueue 是freebsd 的方案,/dev/poll 是最古老的Solaris 的方案,使用难度依次递增。 简单的说,这些api 做了两件事: 1. 避免了每次调用select/poll 时kernel 分析参数建立事件等待结构的开销,kernel 维护一个长期的事件关注列表,应用程序通过句柄修改这个列表和捕获I/O 事件。 2. 避免了select/poll 返回后,应用程序扫描整个句柄表的开销,Kernel 直接返回具体的事件列表给应用程序。 二. libevent库 由于epoll, kqueue, /dev/poll每个接口都有自己的特点,程序移植非常困难,于是需要对这些接口进行封装,以让它们易于使用和移植,其中libevent库就是其中之一。 按照libevent的官方网站,libevent库提供了以下功能:当一个文件描述符的特定事件(如可读,可写或出错)发生了,或一个定时事件发生了,libevent就会自动执行用户指定的回调函数,来处理事件。目前,libevent已支持以下接口/dev/poll, kqueue(2), event ports, select(2), poll(2) 和 epoll(4)。Libevent的内部事件机制完全是基于所使用的接口的。因此libevent非常容易移植,也使它的扩展性非常容易。目前,libevent已在以下操作系统中编译通过:Linux,BSD,Mac OS X,Solaris和Windows。 使用libevent库进行开发非常简单,也很容易在各种unix平台上移植。一个简单的使用libevent库的程序如下:
三.libevent库的应用 Go2代理是一个大流量的代理应用,月流量近TB。其中图片、flash、zip文件占总流量的绝大部分。为了减少流量成本,需要将部分进行分流。开始时,使用了传统的php代理来分流,但Go2并发访问极大,多进程架构的php无法承受,在虚拟主机vps上启动数秒后就立即瘫痪。后改用 python的twisted网络架构,采用了twisted的异步tcp通讯功能。运行一段时间后,发现twisted的异步dns稳定性不太好,经常发生系统级的崩溃。最后,经过分析比较,决定采用libevent库来做Go2 的分流代理应用。 Libevent库支持异步socket,支持异步dns,并本身还带了个简单的http 服务器。Go2 的分流代理应用就是使用了libevent库的以上三个功能。 1、简单的http 服务器:实现的分类代理的用户端的输入,输出管理。 2、异步socket,实现了高并发性的用户接入,和高并发性的目的服务器访问。 3、异步dns,解决了dns查询时的并发性和高效性。 下面简单介绍一下Go2 的分流代理程序的主要流程。源代码请参考 A. 主程序流程 1、使用event_init()初始化libevent环境; 2、使用evhttp_new()生成http服务实例,并使用evhttp_bind_socket聆听指定的端口; 3、使用evhttp_set_cb设置http服务实例的url回调函数。 4、进入libevent的事件循环。每当有用户请求url,libevent将自动调用url所对应的回调函数。 B. 用户请求处理流程:
|