您的位置:首页 >资讯列表 > 正文
发布时间:2020-04-21 17:12
HTTP代理服务器性能设计

  通过比较,我们选定在应用层实现HTTP代理服务器。但是在代理服务器的软件实现中,其内部结构才是决定代理服务器在同等硬件条件下,发挥优性能的关键因素。服务器选择怎样的内部结构,将会影响到整个设计和实施阶段,而且不同的设计结构对服务器的性能的影响也是相当大的。在计算量较少的代理服务器中,影响服务器性能的大因素之一就是I/O的效率,CPU速率与外设速率的不匹配、以及与网络速率差异,导致了I/O问题是实现高性能服务器的关键。


  提高代理服务器系统的I/O性能,高效的处理大规模的并发连接请求和数据转发是实现运营商一级HTTP代理服务器的关键。本章首先阐述了实现代理服务器I/O系统的几种典型模式,分析了各种模式的优缺点,然后从系统整体需求出发,提出在Windows平台下,采用线程池和完成端口相结合的模式对系统I/O部分进行处理。这一模式将大程度的节省系统资源,降低系统编程实现时的复杂度,同时提高稳定性。


  3.2多进程与多线程


  下面将对实现代理服务器I/O系统的几种典型模式进行分析。


  3.2.1多进程


  1)一个请求/进程


  简单的能并发的为多个请求服务的代理服务器就是用fork模式。由一个主进程在简单循环中接受新的连接,然后对每个请求派生一个新的进程来处理这个请求。当请求处理完后,进程退出。这个结构的好处在于主程序可以很简单,因此也就很稳定。另外一个好处就是子进程不用担心内存泄漏,因为每个进程结束时退出,它占有的内存会由操作系统清除。因为每个子进程都是独立的并且处理不同的网络连接,它们之间就不需要同步机制。


  这个结构在负载低的时候能达到比较好的性能。中等负载时在以下情形下也能应付:在进程映像小的情况下,或者用了针对应用的高效措施,或者应用本身不会有太多的并发任务。如果进程使用的缓存和总的进程数不是太多,那么在多


  CPU的环境中也是性能较高的。


  由于fork系统调用有相当大的开销,而且进程是一个系统中大的实体,通常系统中能同时存在的进程数也是有限的,而采用fork模式的代理服务器处理一定数量的连接就要有同等数量的进程来服务,这样不仅消耗的资源可观,还存在上下文切换的较大开销。所以这种结构的处理能力不高,通常在几个到十几个请求/秒。如图3.1所示:


  图3.1一个请求/进程结构


  Fig 3.1 The structure of One request/Process


  2)预创建进程


  这种代理服务器不是对每个请求都创建一个新的进程,而是由用户手动预先创建(pre-fork)一定数量的进程等待处理请求。这些进程一直驻留,可以并行的处理请求。当一个进程处理完一个请求后,再接受下一个请求。如图3.2所示:


  图3.2预创建进程结构


  Fig 3.2 The structure of pre-fork process


  与前者相比,预创建进程的代理服务器消除了fork系统调用的开销。它的性能是相当高的,每秒能处理的连接数是采用fork模式的代理服务器的2至10倍。比起每个请求创建一个进程的服务器来说,预创建进程的代理服务器的实现要十分仔细,容易发生内存泄漏而造成地址空间混乱。程序的稳定性是特别需要注意的问题。


  由于处理请求时它用的一个进程来处理一个请求,要能处理较高的负载也必须加大预创建的进程数,一方面,它的上限受系统的限制,同时它的进程间切换的开销是比较大的。多进程代理服务器另外一个缺点是进程间的资源共享困难。


  3.2.2多线程


  另一种能接受并发服务的方式是用多线程来代替多进程。对每个连接创建一个新的线程,在请求处理完时线程也退出。如图3.3所示。这种方式和fork模式的结构是相似的,不同的是用创建线程来代替创建进程[10]。


  图3.3一个请求/线程结构


  Fig 3.3 The structure of One request/Thread


  使用线程相对于使用进程的优势体现在:一个进程的多个线程在同一地址空间当中,它们之间的数据共享是很容易的;并且,创建一个线程比创建一个进程的开销要小;再有,线程的上下文切换是发生在同一个进程内时就比进程间切换的开销要小。


  因为代理服务器本质上来说是网络I/O驱动的,所有的并发的请求处理过程都是在网络I/O的不同阶段阻塞着。当应用的内核执行体增加到和用户级线程数量相当时(系统通常也对内核执行体有上限的限制)。这种多对少的模式又降级到了一对一的模式。由于这种原因,多线程的服务器的扩展性受到了限制。当然,与多进程服务器来说,多线程服务器消耗的资源要少并且要“轻量级”,所以扩展性还是相对要高。多线程(一个请求/线程方式)服务器在低、中负载下是相当好的,但到了负载很重时,性能也要下降。在多CPU的环境下,性能要比单CPU差,因为在多CPU下的信号锁的开销很大。


  3.2.3单进程事件驱动


  单进程事件驱动(single-process event-driven——SPED)模式采用一个进程来处理并发请求。代理服务器用非阻塞的系统调用来实现异步操作,使用BSD的select()或SVR4的pool()来检查I/O是否已完成。服务器能同时接受并处理多个请求,将每个请求的各个处理阶段穿插在服务器的执行历史中。在每一次重复的过程中,服务器用select或pool来检查是否准备好和完成的I/O事件(新连接到达,或完成了的操作,或收到传来的数据,或发送数据的缓冲区有空间等)。当有I/O事件准备好,就完成和这个事件有关的下一步工作。如图3.4所示:


  图3.4单进程事件驱动结构


  Fig 3.4 The structure of Single-Process Event-Driven


  SPED模式与以上的模式相比,其优势在于没有创建新的进程/线程的开销,较少了上下文切换的开销。还有重要的一点,由于整个代理服务器在一个地址空间内运行,实现进程中的缓存或数据共享是很容易的事。采用该模式的代理服务器只有一个程序执行体,占有的内存空间大大减少[11]。没有了多进程/线程里的进程/线程间的同步和通信,不存在了数据的互斥访问,这也是该模式的一个优势。


  SPED实现的并发性和多进程/线程时的并发性的机制并不是一样的。多进程时服务器是依赖操作系统的时间分片机制在多个进程间共享CPU,并由此在多个连接之间共享CPU。而SPED是使用异步I/O来提供客户间的并发性。


  然而SPED必须由开发人员自己来实现多个请求间的调度和协调,因此加大了SPED服务器实现的难度。而且SPED实现过程中,需要应用程序不断的检查I/O是否完成,使得系统效率降低。


  本系统要实现的HTTP代理服务器是基于3.5G的接入网络,不同于一般局域网的较小型的规模,往往是上千、万台的规模,这种要求下的HTTP代理服务器不仅是硬件上的高性能,在软件设计上也需要大程度提高性能。其系统I/O的选择和改善成为系统实现的关键要素,这也是提高整个系统的可扩展性的必要考虑因素。经过对上述几种代理服务器内部结构的分析,如果可以将这几种结构的优点集中,同时大限度的减少这几种结构带来的缺点,那么就可以达到理想的情况。


  接下来,将对线程池的相关内容进行详细分析。


  3.3线程池


  线程池和操作系统中的缓冲区的概念十分类似,它的工作流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当客户端有一个新请求时,就会唤醒线程池中的某一个睡眠线程,让它来处理客户端的这个请求,当处理完这个请求后,线程又处于睡眠状态。


  3.3.1线程池的应用


  线程池通常应用于需要大量线程来完成任务,且完成任务的时间比较短的情况。HTTP代理服务器处理用户的访问请求就是这样的任务,使用线程池技术是非常适合的。因为单个任务小,而任务数量巨大。


  线程池的应用范围有:


  1)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。


  2)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池的情况下,将产生大量线程,短时间内产生大量线程可能使内存到达极限,出现内存溢出错误。


  3.3.2多线程技术和线程池技术的比较


  HTTP代理服务器的数据转发模块要实现并发数据的转发,有两种技术方案可供选择:多线程技术和线程池技术。


  多线程技术主要解决处理器单元内多个任务并发执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力,但如果对多线程应用不


  当,会从整体上增加系统的开销,降低系统的处理效率。可以举一个简单的例子。


  假设HTTP代理服务器处理一个用户请求的时间为T


  T1为创建处理线程的时间


  T2为在线程中执行数据转发任务的时间,包括线程间同步需要的时间


  T3为线程销毁的时间显然,T=T1+T2+T3。


  可以看出,T1和T3是多线程本身带来的开销,当HTTP代理服务器在处理多个用户同时的访问请求时,如果使用多线程技术,会频繁的创建和销毁线程。这将导致T1和T3在系统处理中占有相当大的比例。这显然突出了多线程的弱点


  (T1,T3),而不是优点(并发性)。


  线程池技术正是关注如何缩短或调整T1,T3时间的技术,以提高HTTP代理服务器程序的性能。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在HTTP代理服务器程序处理用户的访问请求时,不再会有T1,T3的开销了。从而高性能的实现了大规模并发数据的转发。线程池在处理少量的任务时,优势并不明显,但对于大量任务的处理,线程池表现得非常卓越[12]。如图3.5


  图3.5多线程与线程池的比较


  Fig 3.5 Compare multi-thread with thread pool


  3.3.3线程池函数


  微软公司的Windows 2000提供了一些新的线程池函数,使得线程的创建、撤销和基本管理更加容易。新的线程池函数可以提供这几种操作:异步调用函数;按照规定的时间间隔调用函数;当单个内核对象变为已通知状态时调用函数;当异步I/O请求完成时调用函数。


  1)异步调用函数QueueUserWorkItem(),该函数负责将工作项目排队放入线程池中的一个线程中并且立即返回。


  2)按规定的时间间隔调用函数。


  3)当单个内核对象变为已通知状态时调用函数。


  4)当异步I/O请求完成时调用函数。


  以上各种情况的具体函数及其用法参见参考文献[13]。


  3.3.4系统I/O的设计方案


  为了高效处理代理服务器大规模并发的I/O,本文作者提出了一种将线程池和完成端口相结和的系统I/O设计方案。


  具体设计是采用微软在Windows 2000中提供的一个线程池函数BindIoCompletionCallback()来实现的。函数BindIoCompletionCallback()生成一个完成端口对象和一个线程池,将进行异步I/O操作的套接字与该完成对象进行关联。当套接字上的异步I/O操作完成时,完成端口对象就会通知线程池中的一个线程运行在函数BindIoCompletionCallback()指定的处理函数对套接字I/O的结果进行处理。如图3.6所示:


  图3.6完成端口和线程池相结合的I/O模式


  Fig 3.6 I/O mode of completion port and thread pool


  用函数BindIoCompletionCallback()将进行异步I/O操作的套接字都于完成端口相关联,这样当相关套接字上的异步I/O操作完成时,完成端口对象通知系统,系统会自动调用线程池中的一个工作线程执行事先指定的处理函数来处理完成的I/O操作。这种I/O设计方案利用完成端口对象对每个套接字上的I/O操作进行完成通知,简化了实现,而且利用线程池来处理完成的I/O操作,从而极大的提高了代理服务器处理大规模并发I/O的性能。


  3.4小结


  本章主要对系统的I/O性能进行考虑,通过分析几种代理服务器实现的内部结构,设计出本系统的内部结构,即采用完成端口和线程池相结合的模式,这样改进了SPED模型。线程池中线程的创建撤销由操作系统自动管理,避免了由应用程序自己来进行多个请求间的调度和协调。不仅大大简化了编程的复杂度,而且,提高了系统的稳定性,这对一个负载较大代理服务器程序而言,显得尤为重要。在系统对I/O的管理上,采用IOCP模型,改进了SPED模型需要常常查询套接字的I/O状态这一现象,使系统不用在查询I/O状态上耗费时间。通过这些改进,使得系统性能可以在现有硬件条件下发挥出大效率,真正满足对高负载、大用户量的支持。


上一篇 HTTP代理服务器的系统结构和工作流程 下一篇 HTTP代理ip​协议具体实现过程