当服务器程序需要每秒处理大量离散消息或者请求时,该注意哪些问题。网服务器更符合这种情况,但并非所有的网络程序都是
严格意义上的服务器。使用“高性能请求处理程序”是一个很糟糕的标题,为简洁起见,下面将简称为“服务器”。
本文不会涉及到多任务应用程序,在单个程序里同时处理多个任务现在已经很常见。比如你的浏览器可能就在做一些并行处理,
但是这类并行程序设计没有多大挑战性。真正的挑战出现在服务器的架构设计对性能产生制约时,如何通过改善架构来提升系统
性能。
对于在拥有上G内存和G赫兹CPU上运行的浏览器来说,通过DSL进行多个并发下载任务不会有如此的挑战性。这里,应用的焦点不在于
通过吸管小口吮吸,而是如何通过水龙头大口畅饮,这里麻烦是如何解决在硬件性能的制约.(作者的意思应该是怎么通过网络硬件的改善
来增大流量)
一些人会可能会对我的某些观点和建议发出置疑,或者自认为有更好的方法, 这是无法避免的。在本文中我不想扮演上帝的角色;这里所
谈论我自己的一些经验,这些经验对我来说, 不仅在提高服务器性能上有效,而且在降低调试困难度和增加系统的可扩展性上也有作用。
但是对某些人的系统可能会有所不同。 如果有其它更适合于你的方法,那实在是很不错. 但是值得注意的是,对本文中所提出的每一条建议
的其它一些可替代方案,我经过实验得出的结论都是悲观的。
你自己的小聪明在这些实验中或许有更好的表现,但是如果因此怂恿我在这里建议读者这么做,可能会引起无辜读者的反感。你并不想
惹怒读者,对吧?
本文的其余部分将主要说明影响服务器性能的四大杀手:
(1)数据
拷贝(Data Copies)
(2)环境切换(Context Switches)
(3)内存分配(Memory allocation)
(4)锁竞争(Lock contention)
在文章结尾部分还会提出其它一些比较重要的因素,但是上面的四点是主要因素。如果服务器在处理大部分请求时能够做到没有数据拷贝,
没有环境切换,没有内存分配,没有锁竞争,那么我敢保证你的服务器的性能一定很出色。
(1)数据拷贝(Data Copies)
本节会有点短, 因为大多数人在数据拷贝上吸取过教训. 几乎每个人都知道产生数据拷贝是不对的, 这点是显而易见的, 在你的职业生涯
中, 你很早就会见识过它. 现今, 几乎每个大学课程和几乎所有how-to文档中都提到了它。甚至在某些商业宣传册中, "零拷贝" 都是
个流行用语。
尽管数据拷贝的坏处显而易见,但是还是会有人忽视它。因为产生数据拷贝的代码
常常不是很明显,
你知道你所调用的库或驱动的代码会进行数据拷贝吗? 答案往往超出想象。
"程序I/O"在计算机上到底指什么? 好好思考一下。
哈希函数
是另外一个可能产生数据拷贝的地方,在这里能看到访问内存时的拷贝和更多计算上的消耗。
Once it's pointed out that hashing is effectively "copying plus",似乎能够被避免,但据我所知,
有一些非常聪明的人说过要做到这一点是相当困难的。如果想真正去除数据拷贝,不管是因为影响了服务器性能,还是想在黑客大会上展示
"零复制”技术,你必须自己跟踪可能发生数据拷贝的所有地方,而不是轻信宣传。
有一种可以避免数据拷贝的方法是使用buffer的描述符(或者buffer chains的描述符)来取代直接使用buffer指针,每个buffer描述符
应该由以下元素组成:
*一个指向buffer指针和整个buffer的长度
*一个指向buffer中真实数据的指针和真实数据的长度,或者长度的偏移
*以双向链表的形式提供指向其它buffer的指针
*一个引用计数
现在,代码可以简单的在相应的描述符上增加引用计数来代替内存中数据的拷贝。这种做法在某些条件下表现的相当好,包括在典型的
网络协议栈的操作上,但有些情况下这做法也令人很头大。 一般来说,在buffer chains的开头和结尾增加buffer很容易,对整个buffer
增加引用计数, 以及对buffer chains的即刻释放也很容易。在chains的中间增加buffer,一块一块的释放buffer,或者对部分buffer增加
引用技术则比较困难。而分割,组合chains会让人立马崩溃。
我不建议在任何情况下都使用这种技术,因为当你想在链上搜索你想要的一个块时,就不得不遍历一遍描述符链,这甚至比数据拷贝更糟糕。
最适用这种技术地方是在程序中大的数据块上,这些大数据块应该按照上面所说的那样独立的分配描述符,以避免发生拷贝,也能避免
影响服务器其它部分的工作.(大数据块拷贝很消耗CPU,会影响其它并发线程的运行)
关于数据拷贝最后要指出的是:在避免数据拷贝时不要走极端。我看到过太多的代码为了避免数据拷贝,最后结果反而比拷贝数据更糟糕,
比如产生环境切换或者一个大的I/O请求被分解了。数据拷贝是昂贵的,但是在避免它时,是收益递减的(意思是做过头了,效果
反而不好)。
为了除去最后少量的数据拷贝而改变代码,继而让代码复杂度翻番,不如把时间花在其它方面。
(2)环境切换(Context Switches)
相对于数据拷贝影响的明显,非常多的人会忽视了环境切换对性能的影响。在我的经验里,比起数据拷贝,环境切换是让高负载应用彻底
完蛋的真正杀手。系统更多的时间都花费在线程切换上,而不是花在真正做工作的线程上。 令人惊奇的是,(和数据拷贝相比)在同一个
水平上,产生环境切换总是更常见。
引起环境切换的第一个原因往往是活跃线程数比CPU个数多。 随着活跃线程数相对于CPU个数的增加,环境切换的次数也在增加,如果你够
幸运,这种增长是线性的,但更常见是指数增长。这个简单的事实解释了为什么每个连接一个线程的多线程设计的可伸缩性更差。对于一个
可伸缩性的系统来说,限制活跃线程数少于或等于CPU个数是更有实际意义的方案。曾经这种方案的一个变种是只使用一个活跃线程,虽然这
种方案避免了环境争用,同时也避免了锁,但它不能有效利用多CPU在增加总吞吐量上的价值,因此除非程序will be non-CPU-bound,
(usually network-I/O-bound),应该继续使用更实际的方案。
一个有适量线程的程序首先要考虑的事情是规划出如何创建一个线程去管理多连接。这通常意味着前置一 个select/poll, 异步I/O,信号
或者完成端口,而后台使用一个事件驱动的程序框架。关于哪种前置API是最好的有很多争论。 Dan Kegel的C10K在这个领域是一篇不错的
论文。个人认为,select/poll和信号通常是一种正确但是丑陋的方案,因此我更倾向于使用AIO或者完成端口,但是实际上它并不会更好。
也许除了select(),它们都还不错。所以不要花太多精力去探索前置系统最外层内部到底发生了什么。
对于最简单的多线程事件驱动服务器的概念模型, 其内部有一个请求缓存队列,客户端请求被一个或者多个监听线程获取后放到队列里,
然后一个或者多个工作线程从队列里面取出请求并处理。从概念上来说,这是一个很好的模型,有很多用这种方式来实现他们的代码。
这会产生什么问题吗?
引起环境切换的第二个原因是把对请求的处理从一个线程转移到另一个线程,有些人甚至把对请求的回应又切换回最初的线程去做,这真
是雪上加霜,因为每一个请求至少引起了2次环境切换。把一个请求从监听线程转换到成工作线程,又转换回监听线程的过程中,使用一种
“平滑”的方法来避免环境切换是非常重要的。此时,是否把连接请求分配到多个线程,或者让所有线程依次作为监听线程来服务每个连接
请求,反而不重要了。
即使在将来,也不可能有办法知道在服务器中同一时刻会有多少激活线程.毕竟,每时每刻都可能有请求从任意连接发送过来,一些进行特殊
任务的“后台”线程也会在任意时刻被唤醒。那么如果你不知道当前有多少线程是激活的,又怎么能够限制激活线程的数量呢?
根据我的经验,最简单同时也是最有效的方法之一是:用一个老式的带计数的信号量,每一个线程执行的时候就先持有信号量。如果信号量
已经到了最大值,那些处于监听模式的线程被唤醒的时候可能会有一次额外的环境切换,(监听线程被唤醒是因为有连接请求到来, 此时监听
线程持有信号量时发现信号量已满,所以即刻休眠), 接着它就会被阻塞在这个信号量上,一旦所有监听模式的线程都这样阻塞住了,那么
它们就不会再竞争资源
了,直到其中一个线程释放信号量,这样环境切换对系统的影响就可以忽略不计。更主要的是,这种方法使大
部分时间处于休眠状态的线程避免在激活线程数中占用一个位置,这种方式比其它的替代方案更优雅。
一旦处理请求的过程被分成两个阶段(监听和工作),那么更进一步,这些处理过程在将来被分成更多的阶段(更多的线程)就是很自然的事了。
最简单的情况是一个完整的请求先完成第一步,然后是第二步(比如回应)。然而实际会更复杂:一个阶段可能产生出两个不同执行路径,
也可能只是简单的生成一个应答(例如返回一个缓存的值)。由此每个阶段都需要知道下一步该如何做,根据阶段分发函数的返回值有三种可能
的做法:
* 请求需要被传递到另外一个阶段(返回一个描述符或者指针)。
* 请求已经完成。(返回ok)
* 请求被阻塞(返回"请求阻塞")。这和前面的情况一样,阻塞到直到别的线程释放资源。
应该注意到在这种模式下,对阶段的排队是在一个线程内完成的,而不是经由两个线程中完成。这样避免不断把请求放在下一阶段的队列里,
紧接着又从该队列取出这个请求来执行。这种经由很多活动队列和锁的阶段很没必要。
这种把一个复杂的任务分解成多个较小的互相协作的部分的方式,看起来很熟悉,这是因为这种做法确实很老了。
我的方法,源于CAR在1978年发明的"通信序列化进程"(Communicating Sequential Processes CSP),它的基础可以上溯到1963时的P
er Brinch Hansen and Matthew Conway--在我出生之前!然而,当Hoare创造出CSP这个术语的时候,“进程”是从抽象的数学角度而言的,
而且,这个CSP术语中的进程和操作系统中同名的那个进程并没有关系。依我看来,这种在操作系统提供的单个线程之内,实现类似多线程
一样协同并发工作的CSP的方法,在可扩展性方面让很多人头疼。
分享到:
相关推荐
本资源"Linux高性能服务器编程源码"包含的是springsnail负载均衡器的全部源代码,为我们提供了一个深入理解高性能服务器设计与实现的宝贵学习材料。 首先,让我们探讨一下高性能服务器的核心特性: 1. **并发处理*...
《Linux高性能服务器编程》这本书是针对那些希望深入理解并掌握Linux环境下服务器开发技术的...通过学习这本书,读者将能够从多个层面理解和实践Linux高性能服务器编程,从而设计和构建出更为高效、稳定的服务端应用。
《Linux高性能服务器编程》这本书是针对那些希望深入理解如何在Linux环境下构建高效、稳定服务器的开发者和运维人员的宝贵资源。书中的内容涵盖了广泛的Linux系统编程和服务器优化技术,旨在帮助读者提升系统的性能...
### Linux高性能服务器设计的核心知识点 #### 一、引言 在现代互联网服务中,服务器的性能至关重要。随着网络流量的激增以及用户需求的多样化,如何构建一个高性能、高可靠性的服务器成为了许多开发者关注的焦点。...
### 高性能服务器架构设计与调优 #### 一、高性能服务器架构设计概述 高性能服务器的设计与优化是一项复杂的工程活动,旨在确保服务器能够快速、稳定地处理大量数据和请求。设计过程中需要综合考虑多种因素,包括...
"基于多线程的高性能服务器程序的设计" 本文主要讲解了高性能服务器程序的设计,特别是在高并发和大流量下服务器的服务端开发注意事项。文章首先介绍了 IOCP 模型的原理,然后分别使用 Select 模型和 IOCP 模型对高...
在Linux系统中,高性能服务器编程是一项关键技能,它涉及到系统级编程、网络编程以及优化技术。这个名为"Linux高性能服务器编程源码.zip"的压缩包很可能是为了帮助开发者理解和实践这些概念,通过实际的源代码示例来...
《Linux高性能服务器编程》这本书是Linux服务器开发领域的权威指南,由具有丰富经验的Linux软件开发工程师游双倾力打造。本书旨在深入探讨如何利用Linux系统实现高性能的服务器应用,覆盖了网络协议、服务器编程的...
高性能服务器程序设计 高性能服务器程序设计是指在服务器端设计和实现高性能的服务器程序,以满足大规模用户群体和高并发访问的需求。高性能服务器程序设计需要考虑多方面的 factors,包括服务器模型、I/O策略、...
在高性能服务器的底层网络通信模块设计方面,文档中提出的设计方案在I/O完成端口的基础之上进行了封装,目标是创建一个具有高性能和可扩展性的通用网络通信模块。为了达到这样的目标,设计中采用了一些关键的系统...
高性能服务器的架构、选型、集群等
基于Linux构建Hadoop高性能服务器集群 本文档介绍了如何在Linux下构建高性能的Hadoop服务器集群,以满足大数据时代对高性能计算的需求。文章首先介绍了Hadoop框架的重要性,然后详细介绍了如何在Linux下搭建Hadoop...
而Linux高性能服务器编程是构建高效、可扩展服务的关键技术。这份资料集合涵盖了Linux高性能服务器编程的清晰PDF教程和配套源码,旨在帮助开发者深入理解并掌握这一领域的核心技术。同时,还附带了《Effective C++》...
通过对这些源码的深入学习和实践,开发者可以掌握Linux高性能服务器设计的核心理念和技术,从而提升自己的系统级编程能力。同时,这也将有助于理解大型分布式系统的架构和工作原理,为成为高级系统工程师奠定坚实...
在Linux平台上进行高性能服务器编程是IT领域中的一个重要课题,它涉及到网络编程、多线程、内存管理、I/O模型优化等多个方面。C/C++作为底层系统编程的主要语言,提供了高效且灵活的编程能力,使得开发者能够更深入...
Nginx是一款高性能的Web服务器,它以其反向代理、负载均衡、静态文件处理和高效非阻塞I/O模型而闻名。Nginx的设计理念是轻量级、高并发,因此在处理高流量网站时表现出色。以下是Nginx的一些核心知识点: 1. **模块...
以上内容只是高并发高性能服务器设计的一部分,实际项目中还需要结合业务需求,综合运用多种技术手段,不断优化和调整,以达到最佳性能。通过研究提供的源码,你可以深入理解这些技术的实现细节,并将其应用于自己的...
在本篇文献中,作者杨玲提出了高性能网络游戏服务器的架构设计,这一设计针对游戏服务器的物理结构,一般可分为多个区域(区),每个区会包含多个游戏组(组)。游戏组内包括多种不同功能的服务器,例如登录网关...
《Nginx高性能Web服务器》是一本深入探讨Nginx技术的权威著作,它涵盖了Nginx的基础知识、配置技巧以及优化策略。Nginx,以其高性能、轻量级和反向代理能力著称,是现代互联网架构中的关键组件。在本资料中,我们将...