欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入拆解Tomcat & Jetty 笔记

程序员文章站 2022-06-03 09:23:56
...

 深入拆解Tomcat & Jetty 笔记

02 | HTTP协议必知必会

http请求包括:请求行、请求报头、请求正文

http响应包括:状态行、响应报头、报文主体

HTTP/1.0每次请求都会创建一个新的TCP连接,请求完成后连接就被关闭

HTTP/1.1引入HTTP长连接,会在响应头加入Connection:keep-alive,每次请求可以复用这个连接

 

03 | 你应该知道的Servlet规范和Servlet容器

tomcat工作流程

当客户请求资源时,http服务器会用一个ServletRequest对象把客户的请求信息封装起来,调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的url和servlet的映射关系,找到相应的servlet,如果servlet还没有被加载,就用反射机制创建这个servlet方法,并调用servlet的init方法来完成初始化,调用servlet的service的方法来处理请求,把ServletResponse对象返回给http服务器,http服务器会把响应发送给客户端。

 

04 | 实战:纯手工打造和运行一个Servlet

1、编写一个类继承HttpServlet,重写doGet、doPost方法

2、将tomcat lib目录下的servlet-api.jar拷贝过去,servlet-api.jar中定义了Servlet接口,而我们的Servlet类实现了Servlet接口,编译时需要这个jar包

3、编译命令 javac -encoding utf-8 -cp ./servlet-api.jar ./MyServlet.java

4、建立Web应用的目录结构

5、新建web.xml配置servlet或注解方式部署servlet

6、把应用放到tomcat的webapps下并启动tomcat

 

05 | Tomcat系统架构(上): 连接器是如何设计的?

深入拆解Tomcat & Jetty 笔记

深入拆解Tomcat & Jetty 笔记

深入拆解Tomcat & Jetty 笔记

EndPoint接收到Socket连接后,生成一个SocketProcessor任务提交到线程池去处理,SocketProcessor的Run方法会调用Processor组件去解析应用层协议,Processor通过解析生成Request对象后,会调用Adapter的service方法。

 

连接器的作用,比如:

1、监听网络端口

2、接收网络连接请求

3、读取请求网络字节流

4、根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request对象

5、将Tomcat Request对象转成标准的ServletRequest

6、调用Servlet容器,得到ServletResponse

7、将ServletResponse转成Tomcat Response对象

8、将Tomcat Response转成网络字节流

9、将响应字节流写回浏览器

 

06 | Tomcat系统架构(下):聊聊多层容器的设计

深入拆解Tomcat & Jetty 笔记

 深入拆解Tomcat & Jetty 笔记

请求的链式调用是基于Pipeline-Valve责任链来完成的。

 

07 | Tomcat如何实现一键式启停?

LifeCycle接口与组件的生命周期有关,它定义了这么几个方法:init()、start()、stop()、destroy(),每个具体的组件去实现这些方法。在父组件的init()方法里需要创建子组件并调用子组件的init()方法。同样,在父组件的start方法里也需要调用子组件的start方法,这就是组合模式的使用。

如果将来需要增加新的逻辑,直接修改start方法,这样会违反开放封闭原则。我们注意到,组件的init和start方法调用是由它的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化。因此我们把组件的生命周期定义成一个个状态,把状态的转变看成一个事件。而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便地添加和删除,这就是典型的观察者模式。

LifeCycleBase实现了LifeCycle接口中所有的方法,还定义了相应的抽象方法交给具体子类去实现,这是典型的模板设计模式。

下面是LifeCycleBase的init方法的实现

@Override
public final synchronized void init() throws LifecycleException {
    //1. 状态检查
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }

    try {
        //2. 触发 INITIALIZING 事件的监听器
        setStateInternal(LifecycleState.INITIALIZING, null, false);
        
        //3. 调用具体子类的初始化方法
        initInternal();
        
        //4. 触发 INITIALIZED 事件的监听器
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    } catch (Throwable t) {
      ...
    }
}

 

08 | Tomcat的“高层们”都负责做什么?

深入拆解Tomcat & Jetty 笔记

1、Tomcat本质上是一个Java程序,因此startup.sh脚本会启动一个JVM来运行Tomcat的启动类Bootstrap 

2、Bootstrap的主要任务是初始化Tomcat的类加载器,并且创建Catalina。

3、Catalina是一个启动类,它通过解析server.xml,创建相应的组件,并调用Server的start方法

4、Server组件的职责就是管理Service组件,它会负责调用Service的start方法

5、Service组件的职责就是管理连接器和顶层容器Engine,因此它会调用连接器和Engine的start方法

Server在内部维护了若干Service组件,它是以数组来保存的,每添加一个组件就需要创建一个+1长度的数组,并由原来数组复制过去。

 

09 | 比较:Jetty架构特点之Connector组件

深入拆解Tomcat & Jetty 笔记

1、Acceptor监听连接请求,当有连接请求到达时就接受连接,一个连接对应一个Channel,Acceptor将Channel交给ManagedSelector来处理

2、ManagedSelector把Channel注册到Selector上,并创建一个EndPoint和Connection跟这个Channel绑定,接着就不断地检测I/O事件

3、I/O事件到了就调用EndPoint的方法拿到一个Runnable,并扔给线程池运行

4、线程池中调度某个线程执行Runnable

5、Runnable执行时,调用回调函数,这个回调函数是Connection注册到EndPoint中的。

6、回调函数内部实现,其实就是调用EndPoint的接口方法来读数据

7、Connection解析读到的数据,生成请求对象并交给Handler组件去处理

Jetty Connector设计中的特点是使用了回调函数来模拟异步I/O,比如Connection向EndPoint注册了一堆回调函数。它的本质将函数当作一个参数来传递,告诉对方,你准备好了就调用这个回调函数。

 

10 | 比较:Jetty架构特点之Handler组件

Jetty Server就是由多个Connector、多人Handler以及一个线程池组成。

Jetty本质就是一个Handler管理器,Jetty本身就提供了一些默认Handler来实现Servlet容器的功能。

Handler本质分成三种类型:

1、协调Handler,这种Handler负责将请求路由到一组Handler中去,如HandlerCollection,它内部持有一个Handler数组,当请求来时,它负责将请求转发到数组中的某一个Handler中去。

2、过滤器Handler,自己处理请求后再把请求转发到下一个Handler,如HandlerWrapper,它内部持有下一个Handler的引用。所有继承HandlerWrapper的Handler都具有过滤器Handler的特征,如ContextHandler、SessionHandler和WebAppContext等。

3、内容Handler,这些Handler会真正调用Servlet来处理请求生成响应内容,如ServletHandler,如果请求的是静态资源,也有相应的ResourceHandler来处理这个请求,返回静态页面。

 

11 | 总结:从Tomcat和Jetty中提炼组件化设计规范

Tomcat和Jetty的组件化设计,让我们可以通过搭积木的方式来定制化自己的Web容器。Web容器为了支持这种组件化设计,遵循了一些规范,如面向接口编程,用“管理者”去组装这些组件(Web容器提供一个载体Server把组件组装在一起工作,容器通过责任链模式把请求依次交给组件去处理),用反射的方式动态的创建组件、统一管理组件的生命周期,并且给组件生命状态的变化提供了扩展点,组件的具体实现一般遵循骨架抽象类和模板模式。

 

12 | 实战:优化并提高Tomcat启动速度

1、清理不必要的Web应用

2、清理xml配置文件,解析东西越少,速度越快

3、清理jar包,Web应用中的lib目录下不应该出现Servlet API或者Tomcat自身的jar,这些jar由Tomcat提供,如果用maven构建应用,可以对servlet api的依赖指定为<scope>provided</scope>

4、清理其他文件,如不需要的日志文件,同样还有work文件夹下的catalina文件夹,它其实是Tomcat把jsp转换为Class文件的工作目录

5、禁止Tomcat TLD扫描(不需要jsp则禁止,需要则指定扫描那些包含TLD文件的jar包),关闭WebSocket支持,关闭JSP支持,配置Web-Fragment扫描,随机数熵源优化,并行启动多个Web应用

 

14 | NioEndpoint组件:Tomcat如何实现非阻塞I/O?

当用户线程发起I/O操作后,网络数据读取操作会经历两个步骤

1、用户线程等待内核将数据从网卡拷贝到内核空间

2、内核将数据从内核空间拷贝到用户空间

 

阻塞和非阻塞是指应用程序在发起I/O操作时,是立即返回还是等待。而同步和异步,是指应用程序在与内核通信时,数据从内核空间到应用空间的拷贝,是由内核发起还是由应用程序来触发。

深入拆解Tomcat & Jetty 笔记

 LimitLatch:连接控制器,负责控制最大连接数

Acceptor中在一个单独的线程里,它在一个死循环里调用accept方法来接收新连接,一旦有新的连接请求到来,accept方法返回一个Channel对象,接着Channel对象交给Poller去处理。

Poller本质是一个Selector,跑到单独线程里。Poller在内部维护一个Channel数组,它在一个死循环里不断检测Channel的数据状态,一旦有Channel可读,就生成一个SocketProcessor任务对象扔给Executor处理。

Executor是线程池,负责运行SocketProcessor任务类,SocketProcessor的run方法会调用Http11Processor来读取和解析请求数据。我们知道Http11Processor是应用层协议的封装,它会调用容器获得响应,再把响应通过Channel写出。

 

15 | Nio2Endpoint组件:Tomcat如何实现异步I/O?

深入拆解Tomcat & Jetty 笔记

Nio2Endpoint中没有Poller组件,也就没有Selector。这是因为在异步I/O模式下,Selector的工作交给内核来做了。

Java在操作系统异步IO API的基础上进行了封装,提供了Java NIO.2 API,而Tomcat的异步I/O模型就是基于Java NIO.2实现的。 

 

16 | AprEndpoint组件:Tomcat APR提高I/O性能的秘密

APR通过jni调用c语言函数,能显著提高性能。APR提升性能的秘密还有:通过DirectByteBuffer避免了JVM堆与本地内存之间的内存拷贝;通过sendfile特性避免了内核与应用之间的内存拷贝以及用户态和内核态的切换。

 

17 | Executor组件:Tomcat如何扩展Java线程池?

Tomcat扩展了Java线程池的核心类ThreadPoolExecutor,并重写了它的execute方法,定制自己的任务处理流程。同时Tomcat还实现了定制版的任务队列,重写了offer方法,使得在任务队列长度无限制的情况下,线程池仍然有机会创建新的线程。

Tomcat线程池与Java原生线程池的区别在于Tomcat线程总数达到最大数时,不是立即执行拒绝策略,而是再尝试向任务队列添加任务,添加失败后再执行拒绝策略。

 

18 | 新特性:Tomcat如何支持WebSocket?

WebSocket与HTTP协议一样,是一个应用层协议。为了跟现有的HTTP协议保持兼容,它通过HTTP协议进行一次握手,握手之后数据就直接从TCP层的Socket传输,就与HTTP协议无关了。浏览器发给服务端的请求会带上跟WebSocket有关的请求头。比如Connection: Upgrade和Upgrade: websocket。

 

19 | 比较:Jetty的线程策略EatWhatYouKill

jetty将I/O事件的侦测和处理放到同一个线程来处理,充分利用CPU缓存并减少了线程上下文切换的开销。根据Jetty的官方测试,这种名为“EatWhatYouKill"的线程策略将吞吐量提高了8倍。常规的NIO思路是事件的侦测和请求的处理分别用不同的线程处理,好处是它们互不干扰和阻塞对方。但是这种方式可能会使用不到cpu的缓存,它比内存快多了以及它带来了线程切换的开销。

 

20 | 总结:Tomcat和Jetty中的对象池技术

Tomcat用SynchronizedStack类来实现对象池,它内部维护了一个对象数组,并用数组来实现栈的接口。用数组而不用类似ConcurrentLinkedQueue链表来维护对象,可以减少结点维护的内存开销,并且它本身只支持扩容不支持缩容,也就是数组对象在使用过程中不会被重新赋值,也就不会被gc。这样设计的目的是用最低的内存和gc的代价来实现*容器。同时Tomcat的最大同时请求数是有限制的,因此不需要担心对象的数量会无限膨胀。

Jetty中的对象池ByteBufferPool,它本质是一个ByteBuffer对象池,只需在ByteBuffer对象池里拿到一块预先分配好的Buffer,这样避免了频繁的分配内存和释放内存。对象池需要尽量做到无锁化,比如Jetty就使用了ConcurrentLinkedDeque。如果你的内存足够大,可以考虑用线程本地(ThreadLocal)对象池。这样每个线程都有自己的对象池,线程之间互不干扰。

 

21 | 总结:Tomcat和Jetty的高性能、高并发之道

Tomcat和Jetty通过合理的I/O模型和线程模型减少了线程的阻塞。另外系统调用会导致用户态和内核态切换的过程,Tomcat和Jetty通过缓存和延迟解析尽量减少系统调用,另外还通过零拷贝技术避免多余的数据拷贝。高效的利用系统资源还包括另一层含义,就是用一种资源换取另一种资源。Tomcat和Jetty中使用对象池技术就是用内存换取CPU,将数据压缩后再传输就是用CPU换网络。高效的并发编程也很重要,多线程虽然可以提高并发度,也带来了锁的开销,应避免锁,可用原子变量和CAS操作来代替锁,如果实在避免不了用锁,那也要减少锁的范围和强度,比如用细粒度的对象锁或者低强度的读写锁。Tomcat和Jetty的代码也很好的实践了这一理念。

 

22 | 热点问题答疑(2):内核如何阻塞与唤醒进程?

Socket read系统调用的过程:首先在CPU在用户态执行应用程序的代码,访问进程虚拟地址空间的用户空间;read系统调用时CPU从用户态切换到内核态,执行内核代码,内核检测到Socket上的数据未就绪时,将进程的task_struct结构体从运行队列中移到等待队列,并触发一次CPU调用,这时进程会让出CPU;当网卡数据到过时,内核将数据从内核空间拷贝到用户空间的Buffer,接着将进程的task_struct结构体重新移到运行队列,这样进程就有机会重新获得CPU时间片,系统调用返回,CPU又从内核态切换到用户态,访问用户空间的数据。

 

23 | Host容器:Tomcat如何实现热部署和热加载?

有了ContrainBase中的后台线程和backgroundProcess方法,各种子容器和通用组件不需要各自弄一个后台线程来处理周期性任务。有了ContrainerBase的周期性任务处理“框架”,作为具体容器子类,只需要实现自己的周期性任务就行。而Tomcat的热加载,就是在Context容器中实现的。

热部署会重新部署Web应用,原来的Context对象会整个被销毁,这样过程由Context的父容器Host实现的。Tomcat的热部署跟Context不一样,Host容器并没有在backgroundProcess方法中实现周期性检测任务,而是通过监听器HostConfig来实现的,它周期性检测Web应用目录变化。

 

24 | Context容器(上):Tomcat如何打破双亲委托机制?

深入拆解Tomcat & Jetty 笔记

双亲委托机制保证一个Java类在JVM中是唯一的,如果你不小心写了一个与JRE核心类同名的类,如Object类,双亲委托机制能保证加载的是JRE里的那个Object类,而不是你写的Object类。这是因为AppClassLoader在加载你的Object类时,会委托给ExtClassLoader去加载,而ExtClassLoader又会委托给BootstrapClassLoader,BootstrapClassLoader发现自己已经加载过了Object类,会直接返回,不会加载你写的Object类。

Tomcat自定义类加载器WebAppClassLoader打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的就是优先加载Web应用自己定义的类。

Tomcat的loadClass方法过程如下:

1、在本地Cache查找类是否加载过,即查看Tomcat的类加载器是否已经加载过这个类

2、 如果Tomcat类加载器没有加载过这个类,再看系统类加载器是否加载过

3、如果都没有,就让ExtClassLoader去加载,目的是防止Web应用自己的为覆盖JRE的核心类。

4、如果ExtClassLoader加载失败,在本地Web应用目录查找并加载

5、如果本地目录没有这个类,由系统类加载器去加载

6、上述加载全部失败,就抛出ClassNotFound异常

 

25 | Context容器(中):Tomcat如何隔离Web应用?

Tomcat的Context组件为每个Web应用创建一个WebAppClassLoader类加载器,由于不同类加载器实例加载的类是互相隔离的,因此达到了隔离Web应用的目的,同时通过CommonClassLoader等父加载器来共享第三方JAR包。而共享的第三方JAR包怎么加载特定Web应用的类呢?可以通过设置线程上下文加载器来解决。 

 

26 | Context容器(下):Tomcat如何实现Servlet规范?

Tomcat通过Wrapper容器来管理Servlet,Wrapper包装了Servlet本身以及相应的参数,这体现了面向对象中”封装“的设计原则。

Tomcat会给每个请求生成一个Filter链,Filter链中的最后一个Filter会负责调用Servlet的service方法。

对于Listener来说,我们可以定制自己的监听器来监听Tomcat内部发生的各种事件:包括Web应用级别的、Session级别的和请求级别的。Tomcat中的Context容器统一维护了这些监听器,并负责触发。

 

27 | 新特性:Tomcat如何支持异步Servlet?

深入拆解Tomcat & Jetty 笔记

异步Servlet将Tomcat线程和Web应用线程分开,体现了隔离的思想,也就是把不同的业务处理所使用了资源隔离,使得它们互不干扰,尤其是低优先级的业务不能影响高优先级的业务。

 

28 | 新特性:Spring Boot如何使用内嵌式的Tomcat和Jetty?

 

29 | 比较:Jetty如何实现具有上下文信息的责任链?

Tomcat和Jetty都实现了责任链模式,其中tomcat是通过pipeline-valve来实现的,而jetty是通过HandlerWrapper来实现的,将各Handler组成一个链表,如下:

WebAppContext->SessionHandler->SecuritHandler->ServletHandler 

 

30 | 热点问题答疑(3):Spring框架中的设计模式 

 

31 | Logger组件:Tomcat的日志框架及实战

SLF4J门面模式接口规范,Log4j、Logback或Log4j2实现 

 

32 | Manager组件:Tomcat的Session管理机制解析

session是会话的生命周期,每次请求都会重置超时时间,TCP连接超时,连接就被回收。tcp是系统网络层面,而session是应用层面的,应用层面完全由应用控制生命周期,所以tcp和session之间没有什么关系。

 

 33 | Cluster组件:Tomcat的集群通信原理

要保持会话信息在节点之间一致。

基本上有两种方式:

  1. 把所有session数据放到一台服务器或者一个数据库中,集群中的所有节点通过访问这台session服务器获取数据
  2. 在集群中的节点进行session数据的同步拷贝,这里又分两种策略:第一种是将一个节点的session拷贝到集群中其他所有节点;第三种是将一个节点上的session数据拷贝到另一个备份节点 

 

34 | JVM GC原理及调优的基本思路 

年轻代和年老代使用不同垃圾收集算法,比如年轻代采用复制-整理算法,年老代采用标记-清理算法

与CMS相比,G1收集器有两大特点:

  • G1可以并发完成大部分GC的工作,期间不会STW
  • G1使用非连续空间,这使G1能够有效地处理非常大的堆。此外,G1可以同时收集年轻代和年老代。G1并没有将Java堆分成(Eden、Survivor和Old),而是将堆分成许多(通常是几百个)非常小的区域。这些区域是固定大小的(默认大约2MB)。每个区域都分配给一个空间

具体收集过程是:

  • 将所有存活的对象将从收集的区域复制到未分配的区域
  • 为了优化收集时间,G1总是优先选择垃圾最多的区域,从而限度地减少后续分配和释放堆空间所需要的工作量。这也是G1收集器名字的由来--Garbage-First 

GC调优原则 

CMS收集器:合理地设置年轻代和年老代的大小

G1收集器:不推荐直接设置年轻代大小,G1收集器会根据算法动态决定年轻和年老代的大小。因此对于G1收集器,我们需要关心Java堆的总大小。G1有参数-XX:MaxGCPauseMillis=n,用来限制最大gc暂停时间。G1根据先前收集的信息以及检测到的垃圾量,估计它可以立即收集的最大区域数量,避免GC时间超过限制。

 

35 | 如何监控Tomcat的性能?

Tomcat的关键指标有吞吐量、响应时间、错误数、线程池、CPU以及JVM内存

命令行查看Tomcat指标

ps -ef | grep tomcat

查看进程状态的大致信息

cat /proc/<pid>/status

查看tomcat的网络连接,比如Tomcat在8080端口上监听连接请求

netstat -na | grep 8080

通过ifstat来查看网络流量,大致可以看出tomcat当前的请求数和负载状况

对于一个Web应用来说,下游服务的延迟越大,Tomcat所需要的线程数越多,但是CPU保持稳定。所以如果你在实际工作碰到线程数飙升但是CPU没有增加的情况,这个时候你需要怀疑,你的Web应用所依赖的下游服务是不是出了问题,响应时间是否变长。 

 

36 | Tomcat I/O和线程池的并发调优

深入拆解Tomcat & Jetty 笔记

37 | Tomcat内存溢出的原因分析及调优 

 java.lang.OutOfMemoryError: Java heap space

JVM无法在堆中分配对象时,会抛出这个异常,导致这个异常的原因可能有三种

  1. 内存泄漏
  2. 配置问题,指定的堆大小不够
  3. finalize方法的过度使用。finalize方法可以清理对象持有的资源,但是JVM GC不会立即回收这些对象实例,而是将其添加到队列中,执行对象finalize方法,之后才会回收这些对象。finilizer线程会和主线程竞争CPU资源,但由于优先级低,所以处理速度跟不上主线程创建对象的速度,最终抛出OutOfMemoryError。解决办法是尽量不要给Java类定义finalize方法。

在Linux下执行ulimit -a,你会看到ulimit对各种资源的限制

cat /proc/sys/kernel/threads-max                      #这个参数限制操作系统全局的线程数,表明当前系统能创建的总的线程数

参数sys.kernel.pid_max限制系统全局的pid号数值

 

38 | Tomcat拒绝连接原因分析及网络优化

java.net.SocketTimeoutException

超时分为连接超时和读取超时,连接超时往往是由于网络不稳定造成的,但是读取超时不一定是网络延迟造成的,很有可能是下游服务的响应时间过长。

 

java.net.SocketException: Connection reset/Connect reset by peer: Socket write error

一方已将Socket关闭,可能主动关闭或异常退出,另一方还在读写数据。

为了避免异常发生,需要确保:

  • 程序退出要主动关闭所有网络连接
  • 检测通信的另一方关闭连接操作,当发现另一方关闭连接后自己也要关闭该连接

 

java.net.SocketException: Broken pipe

指通信管道已坏。发生这个异常的场景是,通信的一方在收到“Connect reset by peer: Socket write error”后,如果再继续写数据则会抛出 Broken pipe 异常,解决方法同上。

 

java.net.SocketException: Too many open files

指进程打开文件句柄数超过限制。可通过lsof -p pid查看进程打开了哪些文件,是不是有资源泄露。如无资源泄露,可增加最大文件句柄数

 

Tomcat两个比较关键的参数:maxConnections和acceptCount。TCP连接的建立过程:客户端向服务端发送SYN包,服务端回复SYN+ACK,同时将这个处理SYN_RECV状态的连接保存到半连接队列。客户端返回ACK包完成三次握手,服务端将ESTABLISHED状态的连接移入accept队列,等待应用程序(Tomcat)调用accept方法将连接取走。

  • 半连接队列:保存SYN_RECV状态的连接。队列长度由net.ipv4.tcp_max_syn_backlog设置
  • accept队列:保存ESTABLISHED状态的连接,队列长度由min(net.core.somaxconn,backlog)。其中backlog是我们创建ServerSocket时指定的参数

acceptCount是backlog参数,默认是100,net.core.somaxconn默认是128。而Tomcat中的maxConnections是指Tomcat在任意时刻接收和处理的最大连接数。当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会再从accept队列中取走连接,这时accept队列中的连接会越积越多。maxConnections的默认值与连接器类型有关:NIO的默认值是10000,APR默认是8192。所以你会发现tomcat的最大并发连接数等于maxConnections + acceptCount。如果acceptCount设置过大,请求等待时间会比较长;如果acceptCount设置过小,高并发情况下,客户端会立即触发Connection reset异常。

 

修改内核参数,在/etc/sysctl.conf中增加一行net.core.somaxconn=2048,然后执行命令sysctl -p

修改Tomcat参数acceptCount为2048,重启Tomcat,会发现jmeter 1000每秒并发出现的Connection reset异常消失了

 

在高并发中,netstat命令发现有大量tcp连接处于TIME_WAIT状态。这是因为当tcp的一端发起主动关闭,在发出最后一个ack包后,即第3次握手完成后发送了第四次握手的ack包后就进入TIME_WAIT状态,必须在此状态上停留两倍(MSL)报文最大生存时间。期间两端的端口不能使用,必须要超到2MSL时间结束才可继续使用。

 

39 | Tomcat进程占用CPU过高怎么办?

top -H -p <pid>                                  #查看java各线程情况

jstack <pid> > <pid>.log                    #生成线程快照到文件

vmstat 1 100                                     #查看线程上下文切换活动

 

40 | 谈谈Jetty性能调优的思路

操作系统层面调优,我们需要加大一些默认的限制值,这些参数主要可以在/etc/security/limits.conf中或通过sysctl命令进行配置,其实这些配置对于Tomcat来说也是适用的

TCP的发送和接收缓冲区最好加大到16MB,如下命令配置:

 sysctl -w net.core.rmem_max = 16777216
 sysctl -w net.core.wmem_max = 16777216
 sysctl -w net.ipv4.tcp_rmem =“4096 87380 16777216”
 sysctl -w net.ipv4.tcp_wmem =“4096 16384 16777216”

TCP连接队列大小默认为128,在高并发情况下明显不够用,会出现拒绝连接。但值调得过高,因为过多积压会造成请求处理的延迟,推荐设置为4096

 sysctl -w net.core.somaxconn = 4096

net.core.netdev_max_backlog用来控制Java程序传入数据包队列的大小,可以适当调大

sysctl -w net.core.netdev_max_backlog = 16384
sysctl -w net.ipv4.tcp_max_syn_backlog = 8192
sysctl -w net.ipv4.tcp_syncookies = 1

端口

如果Web应用程序作为客户端向远程服务端建立很多TCP连接,可能会出现TCP端口不足的情况。因此最好增加使用的端口范围,并允许在TIME_WAIT中重用套接字

sysctl -w net.ipv4.ip_local_port_range =“1024 65535”
sysctl -w net.ipv4.tcp_tw_recycle = 1

 

高负载服务器的文件句柄数很容易耗尽,这是因为系统默认值通过比较低,我们可以在/etc/security/limits.conf中为特定用户增加文件句柄数

用户名 hard nofile 40000
用户名 soft nofile 40000

 

Jetty本身的调优主要设置不同类型的线程数量,包括Acceptor和Thread Pool

Acceptor的个数accepts应该设置为大于等于1,并且小于等于CPU核数

Thread Pool默认队列是无限的,高负载下容易积压大量待处理请求,应该使用有界队列拒绝过多请求。比如,如果Web应用每秒可以处理100个请求,当负载高峰到业,我们允许一个请求在队列积压60s,那么可以设置队列长度为60*100=6000。一般Jetty的最大线程数应该在50到500之间。

 

41 | 热点问题答疑(4): Tomcat和Jetty有哪些不同?

深入拆解Tomcat & Jetty 笔记