作者:CppExplore http://www.cppblog.com/CppExplore/和http://blog.csdn.net/cppexplore同步发布
一 服务器分类
从软件性能角度,高性能服务器分:cpu密集型服务器/IO密集型服务器
(1)CPU密集型:该类服务器没有对io的访问/没有同步点,性能瓶颈在于对cpu的充分利用。
典型的如转发服务器/代理服务器/协议转换类服务器/分布式总线服务器等。
(2)IO密集型:该类服务器存在对cache/db/硬盘等的同步访问,或者对fcgi/其他服务器等的同步访问。
简单说有同步访问点的均归属此类服务器。当前硬件基础下,有同步操作的服务器,性能瓶颈均在同步点的返回快慢上,而非cpu。
二 网络层机制
对上述两类服务器,均需要同样高效的网络层机制。当前高效的网络层也就是大家熟知的iocp/epoll/kqueue/port/dev.poll等,在各个os下使用宿主os推荐的高效网络层机制,任何通过其他机制绕过这些机制的做法都不可能达到最好性能。这里推荐下boost.asio,文档齐全,示例丰富,学习曲线平缓。
三 CPU密集型服务器设计
(1)单进程单线程是改类服务器的本质特征。
整个进程只存在一个线程,所有代码均运行在同一个线程中,均顺序执行,任何地方不需要加锁。由于网络线程的存在,实际上该类程序的唯一线程就是网络线程,以linux为例,就是epoll线程。
在多核情况下,fork和cpu个数相同的进程数并且如果可能使用sched_setaffinity类函数将进程和cpu绑定。以充分利用多核性能。
该类服务器的代表:tuxedo/nginx.
(2)单进程多线程,但多线程均完成同样的功能,彼此之间互不依赖/互不影响 ,这是该类服务器的变体。
单进程单线程无疑是该类服务器最理想最完美的实现。但有时候为了简化部署,简化业务上报,业务自检,统一日志,尤其是统计类日志/配置动态生效等附加功能考虑,不得已牺牲少许性能而将上述“单进程单线程,fork多个充分利用多核”方案改造为“独立多线程充分利用多核”方案。
该方案中,多线程中的各个线程仍然是顺序执行,任何地方不需要加锁,均为独立的网络线程。
相对方案(1), 该方案编程更复杂,而linux下线程调度又不如进程高效,整体看为方便性牺牲了少许性能。
该类服务器的代表:我们的协议转换网关/分布式总线服务器等。
(3)高效算法
优化耗时较多算法/挑选合适容器,完成固定任务,尽量减少cpu的运算量。
(4)错误设计:区分网络线程/业务线程,将业务线程根据业务特点划分各个线程阶段。
对cpu密集型的服务器来说,关键在于充分利用cpu,尽量减少无用代码的执行。引如中间处理线程,意味着引入锁切换/内存复制/更多无效代码,不可否认,在已有协议栈情况下,根据业务特点化分线程可以简化编程。单纯的单一线程意味着更复杂的编码,尤其是涉及到更多中间状态时。
在该场景下,有位牛人,对线程的点评:“线程是给那些不能将程序执行序转换成状态机的笨人用的” 这句话真是再合适不过了。
四 IO密集型服务器设计
(1)网络层多线程,中间线程按照业务特点设定,同步点操作使用多线程
同步点使用多线程是该类服务器的本质特征。在同步操作的返回时间不能由本服务器控制的前提下,本服务器所能做的也就只能是加多线程数,提供同步并发数。线程数的最优配置取决于网络层入口并发数以及同步操作返回的时间。简单划分可以网络线程数=cpu个数/2.同步点线程数还取决于同步操作的代价,若为廉价的cache操作,则可适当增多,若为昂贵的db操作,则要根据可以分配的连接数决定。
(2)减少人为产生的同步点
尽量减少访问其他系统使用同步接口。
(3)优化同步点
根据同步操作的特点优化: 异步/增大缓存/批量等。
五 内存操作/锁机制/内核态用户态切换/日志操作
(1)内存操作
内存申请:减少内存动态分配,推荐tcmalloc
内存复制:CPU密集型,必须的内存复制:(a)网络读:处从内核态复制到用户态,仅1次 (b)网络写:异步内存复制/用户态到内核态 ,仅2次
IO密集型,内存复制非关键点。
(2)锁机制 CPU密集型:尽量无锁. IO密集型: 非关键点
(3)内核态用户态切换
两类服务器均相同,尽量减少内核态/用户态互相切换:每次调用系统调用尽可能读取更多字符/仅可能减少不必要的系统调用(去除不必要的调用/通过缓存机制减少调用次数)。
(4)日志操作 略
六 进程vs线程vs协程
进程和线程(略)
协程:和进程/线程这种cpu调度单元不同,它更多是线程内对象之间一种调度理念的优化。协程对象有自己的堆栈,可以通过直接跳转直接转换执行点,减少了内存寻址操作。它特别适合用来优化线程内的某些基础组件,包括:状态机/调停者模式(或者线程内队列)。
在CPU密集型服务器的设计中,说道“线程是给那些不能将程序执行序转换成状态机的笨人用的”,而有了协程,我们有了一种新的简化编程的方法。将协程用于网络层,可以手动实现类似select的功能,用于多对象参与的复杂中间状态,可以简化编程。
但从整体性能角度看,协程则是鸡肋的存在,从几年前出现boost.Coroutine,到现在该项目停止开发,boost引入更多其他方案asio/mpl/statechart,协程一路蹒跚。
七 总结
在当前硬件体系架构下,服务器性能的关键仍然是传统的cpu/io/memory.
cpu密集型的服务器,需要最大限度充分利用所有cpu,以及尽量少的进行内存申请/内存复制。
IO密集型服务器,需要最大限度提高io能力,为达到该目的,可以在非同步线程牺牲对cpu的利用率/牺牲对memory的高效使用,一切为提高io并发能力服务。
八 后记
特别感谢张杰同学代替我编译探测程序代码,让我有时间码点文字。