Unix/Linux编程:服务器中的预分配机制

引入

  • 通常,一个并发的、面向连接的服务器,会为每个来自客户的连接创建一个进程。但是,每个TCP的实现都限制了活动的连接数,并且每个操作系统都限制可用的线程数,当服务器达到其中一个限制时,系统将拒绝其请求更多的资源
  • 一般来说,大多数利用传入请求来触发并发性的增长(这也叫做需求驱动,并且认为并发等级随需求增加):因为需求驱动服务器只在需要的时候才使用资源,而且可以处理多个请求而不需要等待一个现有请求被处理完毕,这样就能提供明显低的响应时间(此时,任意时刻的并发等级反映了服务器已经收到,但还未处理完毕的请求数目)
  • 但是,为每个请求创建一个新线程的开销确是昂贵的,而且需要花费相当多的时间。处理延迟了对请求的处理,创建一个进程还需要消耗系统资源。因此,在传统的单处理器上,当操作系统超级一个新进程或者线程并切换环境时,服务器将不会执行。
  • 因此,请求时延可能造成比较大的问题:如果有很多请求几乎同时到达,它们就必须等待服务器创建对其进行处理的进行。如果额外的一些请求到来的速率比服务器请求的速率还快,时延将会累积起来
  • 从短期来看,时延只影响可观察到的响应时间,而不会影响整体吞吐量:如果多个请求几乎在同一时刻到达,操作系统的协议软件将把他们放入队列(如果是TCP那么请求排队,如果是UDP将使到达的数据报排队)然后等待处理
  • 从长远来看,额外的请求将使得请求丢失:在并发版本中,协议软件中的队列最终会排满,并将开始拒绝再来的请求
    在这里插入图片描述
  • 服务器的并发等级:在某个给定时刻一个服务器正在运行的执行线程总数。为处理传入请求,服务器需要创建一个线程,处理完请求后,从线程要退出。因此,服务器的并发请求会随着时间编号。程序员并不关心某个时刻的并发等级却关心服务器的整个生命期间,它所展现出的并发等级的最大数值

从线程/进程预分配

一种直截了断的技术可以用于控制延迟、限制最大并发等级,并且当进程创建时间较长时,也能使并发服务器维护高吞吐量。此技术是预分配并发进程或者线程,这样避免了创建进程/线程所要付出的代价。线程一旦创建、就会持续的运行(即不退出)

为使用预分配技术,设计人员需要这样编写主服务器,即它在开始执行时就创建N个从线程/进程,每个从线程/进程都使用操作系统中所提供的设施以等待请求到达。当请求到达后,一个等待的从线程/进程就开始执行并处理该请求。完成请求后,从线程/进程不退出,而是返回到等待请求的那段代码处

预分配的主要优点是操作系统的额外开销比较低。由于服务器不需要在请求到达时创建从线程/进程,它可以更快的处理请求。当请求的处理涉及IO多于计算时,此技术就很有用了。预分配允许服务器系统在等待与前一个请求相关的IO活动时,切换到另一个从线程/进程,并开始处理下一个请求。即:

当使用预分配时,服务器在启动时就创建若干个并发的从线程/进程。预分配避免了在每次请求到达时创建进程的开销,因而降低了服务器的时延,同时允许在处理一个请求时,与另一个请求相关联的IO活动也在重叠进行

尽管可降低进程创建的开销,但持续性确有一个缺点:程序员必须对资源的使用相对小心。因为:一个在每次到达一个请求时,就分配少量内存的持续运行的从线程/进程。完成该请求的处理后,从线程/进程并不释放内存。最终,将导致地址空间被耗尽

延迟的从线程/进程分配

虽然预分配可以提高效率,但它不能解决所有问题。在某些情况下,使用一种相反的方法却能提高效率:延迟从线程/进程的分配。

要理解延迟如何能起作用,回想一下从线程/进程创建需要时间和资源。只有当创建额外的从线程/进程能提高系统吞吐量或者降低延时,这样做才是合理的。创建一个从线程/进程不仅花时间,也为管理从线程/进程的操作系统部件带来了额外开销。另外,预分配多个试图接入传入请求从线程/进程可能会给网络代码增加额外开销。

如果创建一个从线程/进程的开销少于一个请求的开销,那么并发会降低延时。如果处理请求的开销较小,那么循环方案更好。但是,所需的时间可能与请求有关(比如获取一个DSN域名(数据库)时间与查询有关)。因此,程序员不是总能知道什么时候使用循环什么时候使用并发。

另外,程序员可能不知道是否能快速的找到差错。原因:当请求到达时,服务器软件就检查此报文,以验证报文各字段的值是否合法,并且验证客户是否被授权发出请求。验证可能花几微秒,或者它可能引发进一步的网络通信,从而多花一个数量级的执行时间。一方面,如果服务器检测到报文中有差错,它将快速的拒绝请求,使得处理此报文所需的全部时间可以忽略。另一方面,如果服务器接收到一个有效的请求,它就可能消耗很客观的处理时间。在处理时间短的情况下,并发处理不会得到认可,一个循环服务器将表现出更低的时延和更好的吞吐量

解决方法是采用一种延迟的从线程/进程分配技术。其思想是:该方法不是选用一个循环的或者并发的设计,而是运行服务器测量处理的开销,然后动态的选用循环处理或者并发处理。选择是动态的,因为在处理不同的请求时选择可以不同。

为实现动态的、延迟的分配,服务器经常通过测量逝去的时间来估计处理开销。主服务器接收请求,设置计时器,然后循环的处理请求。如果在计时器到期前,服务器已经完成处理请求,并让从进程处理请求。总之:

当使用延时的从线程/进程分配时,服务器将开始循环的处理每个请求。仅当处理要花大块时间时,服务器才创建一个并发的从线程/进程处理该请求,这种时延允许主服务器在创建一个进程或切换环境前,先检测有无差错的处理一些短请求

在Linux中,延迟分配从线程/进程不难,由于Linux含有一个告警(alarm)机制,主线程可以设置一个计时器,并设法在计时器到期时执行一个过程。由于fork函数允许一个新创建的进程从父进程中继承打开的套接字以及执行程序和数据的副本,主进程可以创建一个从进程,该从进程敲好从主进程所执行的代码处继续进行处理。

技术的结合

预分配和延迟分配基于同一原理:预分配提高了在请求到达前服务器的并发等级,延迟分配提高了在请求到达后服务器的并发等级。即它们都是通过把服务器的并发等级从当前活跃的请求数目中分离出来,设计人员可以获得灵活性并提高服务效率。

预分配和延迟分配技术可以结合使用。服务器一开始可以没有预分配的从进程/线程,并可使用延迟分配。它等待请求到达,并且如果处理花费了很长时间(即定时器到期),就只创建一个从线程/进程。但是,一旦创建了从线程/进程,该从线程/进程不必立即退出;从线程/进程可以认为自己是永远分配的,并继续运行。处理完一个请求后,从线程/进程可等待下一个请求到达

结合的系统的最大问题是需要对并发性进行控制。何时应创建新增的从线程/进程是很容易知道的,但何时从进程/线程应该退出而不是继续运行就很难知道了。一种可能的方案是设法让主线程/进程在创建一个从线程/进程时,指明其最大增长值M。从线程/进程可创建最多M个从线程/进程,这些从线程/进程可创建零个或多个线程/进程。因此,系统开始时只有一个主线程,但是最终会到达固定的并发等级最大值。另一种控制并发的技术是设法让一段时间内不活跃的从线程/进程退出。从线程/进程在等待下一个请求前启动计时器,如果在请求到达前计时器到期,从线程/进程就退出。

如果各个线程出于一个进程中,这些从线程可以使用共享内存等设施来协调其活动。它们可以存储一个共享的整数用来记录任意时刻的并发等级,并且可以使用该整数的值来决定在处理请求后是退出还是继续。在某些系统中,如果允许用户找出在某个套接字上排队的请求书,从线程/进程也可以使用该队列长度来帮助确定并发等级

总结

有两种主要的技术能使设计人员提高并发服务器的性能:预分配和延迟分配

预分配是设法在需要从线程/进程之前就先创建号了一些,从而优化了延迟。主服务器为所要使用的熟知端口打开一个套接字,然后预分配所有的从进程/线程。因为从线程/进程继承了对该套接字的访问,它们都可以等待某个请求的到来。系统将每个收到的请求交给其中的某个从线程/进程。预分配对于并发的无连接服务器很重要,因为处理一个请求的所需时间通常比较短,这使得创建线程/进程的开销相对较大。预分配方案也使得在多处理器上的并发,无连接设计方案效率提高。

延迟分配使用一种缓慢的方法处理分配。主服务器一开始循环的处理每个请求,但设计一个计时器,如果主线程/进程完成处理前计时器到期,就创建一个并发的从线程/进程来处理请求。在各个请求的处理时间不同的情况下,或者当服务器必须检查一个请求是否正确时(即验证客户是否被授权),延迟分配都可以很好地工作。对于短的请求或者含有差错的请求,延迟分配消除了额外开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值