文章转自http://softtest.chinaitlab.com/qtjs/801881.html
当应用遇到规模化问题的时候,就是考虑性能优化的时候了。今天同事和我聊起了NIO在客户端的使用与BIO有什么优势,也勾起了我前一阵子和其他同学交流优化的一些想法,纯粹个人的一点想法。
CPU利用率和Load
在过去做压力测试的时候,我们经常会关注两个指标,CPU和Load。有同学觉得CPU利用率上去了Load肯定也上去了,Load上去了CPU利用率同样会上去。但是在一些需要优化的场景下,常常会看到Load很高,CPU利用率却可能比较低(多核更是可能出现分配不均的情况)。Load其实就是等待处理的任务队列,当你的应用在等待同步消息返回处理的同时,CPU还是会将时间切片分配给这些线程,而真正需要CPU的线程,却不得不在到了时间片以后暂时放弃工作被挂起。因此在程序设计的时候就要考虑如何利用好CPU的这个资源,如何均匀的将压力分摊到各个CPU上(有时候就一个线程在不断循环,导致单个CPU负荷很高)。
NIO在客户端的使用
Http消息设置keepalive和采用NIO的方式复用信道、BIO结合连接池的方式,最基本的目的就是降低建立TCP产生握手的成本,最大限度的复用已有的资源,但是否NIO就只有复用信道这点呢?
NIO和BIO在数据传输和处理的模式上有不同,NIO采用的是BufferPacket+Channel的模式,这其实和操作系统本身的传输模式很类似,而BIO的Stream的模式是Java自己独特的模式。在采用NIO的这种数据传输模式以后,可以充分利用操作系统本身对传输的优化,因此这是一方面好处。另一方面异步和事件机制的使用,可以降低对于昂贵的资源申请,在高并发下提高处理能力。
NIO客户端的编程模型最大特点:依赖反置,松耦合带来性能提升。在请求流程协议中支持“票根”,也就是我们说的回执。例如,你今天面试完了,不需要你在阿里巴巴前台等着结果,直接留个电话,有消息就会直接通知,电话就是通知结果和服务请求者的关联手段。(此时阿里巴巴前台和会议室就会有足够的空间给其他人来面试,这就是资源)
服务端使用NIO就不多说了,这里主要说一下在客户端的使用场景。两者是否真的有很大的差别,是否NIO有绝对的优势,其实还是和场景有关。简单说来就一个判断标准:应用对于通道的利用率是否够高。下面列了4种场景:
1. 一次请求数据量很少,服务处理速度很快。
2. 一次请求数据量很多,服务处理速度很快。
3. 一次请求数据量很少,服务处理速度很慢。
4. 一次请求数据量很多,服务处理速度很慢。
场景1,传输效率很高,服务处理速度很快,一次请求很快就被完成,采用NIO和BIO,在性能优势上除了操作系统对NIO的优化以外,BIO连接池不输于NIO。在易用性上,BIO更加容易处理。(NIO的异步机制,就要求消息传输协议需要有会话码来提供异步处理入口选择如何处理)
场景2,传输过程比较长,消耗时间比较多,服务处理速度很快,因此交互的时间大部分都还是在数据通道传输上,由于NIO在传输过程中依然是串行化的,因此BIO的连接池优于NIO,同时NIO一个客户端只有一个通道,因此BIO开的连接池越大,并行处理能力越强,因此BIO效率比较好一些。
场景3,传输量比较少,服务处理比较慢,很明显这是通道利用率低的表现,NIO有绝对的优势,特别是在高并发下。信道和服务端客户端资源被充分利用。
场景4,传输量比较多,服务处理也比较慢,这时候可以发现信道利用率取决于服务事件和传输消耗时间的比例,这类场景某些情况下BIO也会优于NIO。
单线程和多线程
在使用多线程来优化程序的时候,是否考虑过多线程的使用场景,多线程不是万能药,在某些情况下还可能是毒药。使用多线程的过程中,需要考虑这么几个因素:
1. 资源竞争,复杂度增加。
为什么前面提到的NIO客户端在处理数据流发送和读取的时候都是采用单线程,数据流的发送和读取都是在一个数据通道上的,而读取和发送本身时间消耗是固定的(不论是多线程还是单线程),同时增加了复杂度(需要处理数据包整合问题)。这其实就是在资源上的串行化操作直接导致了任务的串行化,因此任务多线程反而起到了反作用。
2. 是否是关键路径的工作,占关键路径的比例。
首先,在优化以前需要考虑优化的内容是否是关键路径的工作,如果不是,那么增加复杂度实现的多线程模式,就没有价值。其次就是看是否是在关键路径中占有比较大的比例,同样的,还是投入产出比例(多线程带来的复杂度以及在高并发下的一些资源保护措施都需要很多的维护成本)。
3. 任务的合理切分。
在NIO的客户端,接受数据的事件将会写得很轻量级,但是接受到数据然后分析数据还原成业务对象,则会通过线程池的方式来分别处理。就好比监听连接到来,和实际的去建立连接分成了两个阶段的任务,让事件型的任务单纯,快速执行,让与业务相关的部分通过多线程并行的方式提高处理效率。总的来说就是把任务划分成为系统性的任务和业务性的任务,前者消耗时间少,设计尽量简单高效,采用单线程处理即可,后者通常情况下在处理流程和资源上不冲突的情况可以通过多线程并行提高效率。
优化应用关注点:
A. 关键路径是否可以优化,关键路径的任务拆分。
B. 关键路径上的单个任务是否可以拆分并行执行。(是否有资源竞争,是否会有流程上的前后依赖,是否增加复杂度引入新的不稳定因素)
C. 系统资源和依赖外部系统是否会成为瓶颈。(单机的CPU,IO都会在一定的压力下成下降趋势,并行执行反而降低了处理能力)
因此,可以看到不论是MapReduce设计下的Hadoop,还是Erlang语言级别的特性,都尽量的希望任务之间可以并行执行,相互之间低耦合,通过异步事件消息通知方式来交互,同时数据没有共享,防止资源竞争导致无法并行高效处理。系统设计还是要根据场景来判断使用什么方式优化,越简单越好。