高性能程序就是高效的利用CPU、内存、网络和磁盘等资源,在短时间内处理大量的请求。那如何衡量“短时间和大量”呢?其实就是两个关键指标:响应时间和每秒事务处理量(TPS)。
那什么是资源的高效利用呢? 我觉得有两个原则:
- 减少资源浪费。比如尽量避免线程阻塞,因为一阻塞就会发生线程上下文切换,就需要耗费CPU资源;再比如网络通信时数据从内核空间拷贝到Java堆内存,需要通过本地内存中转。
- 当某种资源成为瓶颈时,用另一种资源来换取。比如缓存和对象池技术就是用内存换CPU;数据压缩后再传输就是用CPU换网络。
Tomcat和Jetty中用到了大量的高性能、高并发的设计,我总结了几点:I/O和线程模型、减少系统调用、池化、零拷贝、高效的并发编程。下面我会详细介绍这些设计,希望你也可以将这些技术用到实际的工作中去。
I/O和线程模型
I/O模型的本质就是为了缓解CPU和外设之间的速度差。当线程发起I/O请求时,比如读写网络数据,网卡数据还没准备好,这个线程就会被阻塞,让出CPU,也就是说发生了线程切换。而线程切换是无用功,并且线程被阻塞后,它持有内存资源并没有释放,阻塞的线程越多,消耗的内存就越大,因此I/O模型的目标就是尽量减少线程阻塞。Tomcat和Jetty都已经抛弃了传统的同步阻塞I/O,采用了非阻塞I/O或者异步I/O,目的是业务线程不需要阻塞在I/O等待上。
除了I/O模型,线程模型也是影响性能和并发的关键点。Tomcat和Jetty的总体处理原则是:
- 连接请求由专门的Acceptor线程组处理。
- I/O事件侦测也由专门的Selector线程组来处理。
- 具体的协议解析和业务处理可能交给线程池(Tomcat),或者交给Selector线程来处理(Jetty)。
将这些事情分开的好处是解耦,并且可以根据实际情况合理设置各部分的线程数。这里请你注意,线程数并不是越多越好,因为CPU核的个数有限,线程太多也处理不过来,会导致大量的线程上下文切换。
减少系统调用
其实系统调用是非常耗资源的一个过程,涉及CPU从用户态切换到内核态的过程&#