关于这本书的简单介绍:
注意,在这里我们讨论的高性能服务器软件设计并不是通常意义上的高性能web程序设计,比如说在J2EE、.NET框架下如何使用线程池提高性能,如何优化ASP,PHP程序,或者如何调整apache,IIS等Web服务器以获得更好的性能等等。
这里讨论的是真正高性能的从硬件到操作系统底层,然后到IO模型、应用模型的服务器设计,当然,作为一家之言,里面同样充满了误解、偏见和无知,但是在指责我之前,请仔细思考你指责的理由,进行仔细的测试之后把你的完整意见告诉我,我会很感谢这种建设性的指责而不是其他。
我们面临的问题
目前已经存在,而且将会越来越多的大量网络应用,它们包括:
即时聊天服务器
FTP服务器
基于互联网的媒体应用
大型购物网站
大型门户网站
在线网络游戏
而在实际使用的时候,我们经常碰到下面的情况:
连接数过多,网站无法访问。
无法下载文件,或者下载速度非常缓慢
同时连接的数量受到很大限制。
站点非常脆弱,经常受到这样那样的攻击而瘫痪。
对于这些情况,通常的解决方案是:
增加硬件的性能
使用服务器集群和负载均衡技术
更大的带宽
这些真的需要吗?
我们有没有仔细的思考我们的硬件本身的限制,操作系统的限制,我们的应用程序的限制,真正的瓶颈在哪里?
让我们首先来看一看基本的c/s网络应用。
基本的客户/服务器网络应用系统:
第一类 非基于连接的系统
电子邮件,百万级的,使用的频率有限,同时访问的用户有限,连接的数量受到限制
即时聊天、视频或者其他在线媒体
这些系统有自己的功能限制。
基于连接的静态系统
ftp下载
http访问
基于连接的动态系统
包括动态页面的Web 站点,动态的内容和用户无关
包括用户的概念以及用户相关数据的系统,经常有一个后台数据库。
包括业务逻辑的系统,例如电子购物站点,信息港,以及最近流行的在线游戏。
根本的问题是什么
如何以尽可能少的cpu 时间,内存占用,支持更多的网络连接,发送/接收更多的数据。
从下往上系统的根本构造包括:
1. 硬件设施:包括CPU 的计算能力,硬盘、总线的带宽,网络设施的吞吐能力。
2. 操作系统:操作系统对并发连接的支持和开销,操作系统的IO开销
3. 网络应用模型:基本的网络IO模型,事件通知的机制
4. 应用模型:结合实际应用的应用系统设计
5. 编码和实现:好的编码和差的实现之间的差别是非常巨大的,在这里我提出一些可能有偏见或者偏激的看法。
1,2硬件设施和操作系统的开销比较。
我们将1,2结合起来进行比较:
选择下面一些基本的硬件平台:
典型的笔记本电脑,P4M平台,100M网卡,512M内存
典型的台式机,P4平台,100M网卡,512内存
典型的服务器,xeon平台,千兆网卡,1G内存
典型的amd64位服务器,千兆网卡,1G内存
软件系统包括:
windows系统:
Windows2000 Profession
Windows2000 Advance Server
自由的unix类系统:
Linux 2.4内核
Linux 2.6内核
Freebsd5.1内核
主要的性能评估包括:
影响服务器性能的操作系统的基本调用评估
l 分配/释放内存的开销
l 创建/中止线程/进程的开销
l 互斥锁的开销(上下文切换的开销)
l 内存映像文件的创建/读写开销
操作系统网络性能的基本评估
l 套接字的创建/释放开销
l 套接字的绑定开销
l 建立网络连接的开销
l 发送数据/接收数据的吞吐量
l 一次标准HTTP请求/响应的延时和开销。
初步的结果表明,硬件方面,Xeon系统显然超过了通常的笔记本和台式机,而AMD 的64位系统在较低的主频下有非常好的性能表现(我们仍然使用32位软件进行测试)。而操作系统方面,所有的类Unix系统都全面超过Windows平台,而Linux2.6内核是各个平台中表现最好的。
网络IO模型的设计和评估:
基本概念,操作系统采用何种方式通知应用软件应该去某一个套接字上获取数据或者发送数据。
最简单的办法,采用轮询的机制循环检查套接字的状态,在很多时候,这种方式的效率反而最高。
轮询方式具体的应用范围:
其次,使用每个连接一个线程的方式,这种方式可行性决定于
操作系统可以使用线程的数量
操作系统线程创建和线程间切换的开销。(初步的测试表明,linux2.6内核每秒可以创建5000以上的线程)
两种基本的消息触发方式:
条件触发和边沿触发
常见的select就是条件触发。
Window平台:
包括常见的Select模型
event select模型
IO完成端口模型,实际上就是一种边沿触发的机制。
Linux平台
基本的select模型,最大的问题在于寻找哪一个socket上面发生的事件(socket的本质是文件句柄,所以数量巨大)
边沿触发:
2.4内核推荐的实时信号模型
2.6内核推荐的epoll模型
FreeBSD
基本的select模型
边沿触发的kqueue模型
性能评估:
基本的测试表明,在同时并发连接数量,每个连接的延时和吞吐量上,Linux 2.6内核都是胜利者,其性能显著超过了2.4内核以及FreeBSD。所有的类Unix系统都将Windows系统远远抛在后面。在各种IO模型的比较下, linux2.6内核的epoll同样成为胜利者,而IO完成端口仍然是最后一名,显然,对易用性和图形性能的要求使得Windows并不适合作为一个服务器端的操作系统。
应用系统的模型:
基本的Web服务器:
1静态页面的提供:
内存映像文件
直接文件发送,减少了文件的数据从核心拷贝到应用层然后再拷贝到核心的开销
在Windows和Linux环境下都有相应的系统调用。
核心服务器,直接在核心完成基本的HTTP服务:
khttpd
2动态内容的提供:
基本的硬编码方式提供动态页面
使用简单的应用程序密切相关的脚本语言,
使用通用的脚本语言:
高性能的LUA
高性能但是过于庞大的Java
其它脚本语言PHP,ASP等等。
3 数据库系统
本地存储还是通过网络访问数据库服务器?
ORACLE、Sybase、sql SERVER和其他的数据库服务器
MYSQL、mSQL、Postgres等open source数据库服务器
本地:
Berkely db
如果不需要复杂的事务处理和恢复功能,可以使用简单高性能的,基本B *Tree存储机制。
4 J2EE和.NET框架,复杂性的好处和代价。
其他应用服务器:
数据库应用
在线游戏
流媒体播放,RTP和其他并不广为人知的协议
下一代应用?P2P和BT
新的思路,带来的好处,当然也会由此带来新的问题:
可能对整个互联网带宽和流量的影响
可能带来的法律问题
下面该看看应用系统了,好的设计同样是非常重要的:
真正高性能的程序设计
more small, more fast
more simple, more fast
更小的代码更快
更简单的代码更快
现代计算机的体系,速度往往取决于CPU cache 的命中情况。
因此,更小、更简单的代码往往会获得更好的性能。
l 删除冗余代码,和一般理解相反,放在那里不动的代码即使没有使用也会影响系统的性能。
l 不要低估或者高估编译器的优化,除了代码本身所能提供的信息以外,编译器永远不知道你到底要做什么。
l 不要梦想复用,在代码级重复使用你代码的可能性接近于0
l 层层的封装是效率的杀手
l 动态运行时解析,类型….带来的问题远远大于所承诺的好处。
l 专用、专用再专用,把你的代码限制在一个非常具体的场景中,通用的代码往往意味着低效、潜在的误用和其他错误。
l 不要做期待之外的事情,所有的bonus都是要付出代价的!
l 优化最常见的情况,而不是最糟糕的情况。
l 内存分配和释放是非常昂贵的操作(从时间上,稳定性上都是)
l 不要想象,使用工具观察你的代码,vTune或者gProf等等,只有这样才能发现真正的瓶颈所在。