当前位置: 首页 > 科技观察

接口流量突然增加,如何优化性能?

时间:2023-03-16 13:27:34 科技观察

大家好,我是树哥!对于提供接口服务的应用,很多都是使用SpringBoot默认的Servlet容器Tomcat。上线之初,因为大部分流量较小,所以我们不会对Tomcat做特别的参数调整。但是,随着流量的增加,应用的性能指标越来越差。这个时候,我们大多数人都会选择扩容。除了扩容,我们还可以选择对Tomcat进行性能调优,在不增加成本的情况下提升性能。如果面试官问你,流量突然变大的时候,你一般会做什么,你只会回答扩容,但是太糟糕了。今天树哥就和大家简单分享一下如何对Tomcat进行简单的性能调优,提升应用性能!组件架构要调整Tomcat的性能,我们需要了解它的组件架构。Tomcat的组件架构如下图所示:Tomcat组件结构图从上图可以看出,Tomcat将其业务抽象为Server、Service、Connector、Container等组件,每个组件都有不同的作用。服务器组件。Server组件是Tomcat的最外层组件,是Tomcat实例本身的抽象,代表Tomcat本身。一个服务器组件可以有一个或多个服务组件。服务组件。Service组件是Tomcat中提供服务和处理请求的一组组件。一个服务组件可以有多个连接器和一个容器。MultipleConnector表示它可以使用多种协议同时接收用户请求。连接器组件。Connector负责处理客户端连接,它提供各种服务协议的支持,包括:BIO、NIO、AIO等,它存在的价值在于为Container屏蔽了多协议的复杂性,统一了Container的处理标准.容器组件。Container组件是负责具体业务逻辑处理的容器。当Connector组件与客户端建立连接后,将请求转发给Container组件的Engine组件进行处理。至此,Tomcat的核心组件就基本完成了。实际上,Container组件中还有很多细分的组件。其实如果对业务的抽象感兴趣,可以继续看下去。引擎组件。Engine组件代表一个可运行的Servlet实例,包括Servlet容器的核心功能,可以有一个或多个虚拟主机(Host)。它的主要作用是将请求委托给合适的虚拟主机进行处理,即根据URL路径的配置匹配合适的虚拟主机进行处理。主机组件。Host组件负责运行多个应用程序,它负责安装这些应用程序。它的主要作用是解析web.xml文件,匹配到对应的Context组件。上下文组件。Context组件代表了具体的Web应用本身,它最重要的作用就是管理里面的Servlet实例。一个Context可以有一个或多个Servlet实例。包装组件。一个Wrapper组件代表一个Servlet,它负责管理一个Servlet,包括Servlet的加载、初始化、执行和资源回收。Wrapper是最低级别的容器。可以看出Host是虚拟主机的抽象,Context是应用程序的抽象,Wrapper是Servlet的抽象,Engine是处理层的抽象。核心参数在了解核心参数之前,我们需要对Tomcat对请求的处理流程有一个大概的了解。Tomcat的请求处理流程如下:首先客户端向Tomcat服务器发起请求,Connector组件监听请求,然后与客户端建立连接。接下来,Connector将请求封装起来,转发给Engine组件进行处理。最后Engine组件处理后将结果返回给Connector,Connector组件再将结果返回给客户端。上面的过程可以用下图表示:Tomcat核心参数图上图中有三个非常关键的核心参数,这些关键参数也是性能调优的关键,它们是:acceptCount:当Container线程池达到时当没有空闲线程且Connector队列达到最大数量时,操作系统可以接受的最大连接数量。maxConnections:当Container线程池达到最大数量且没有空闲线程时,Connector的队列可以接收的最大线程数。maxThreads:Container线程池的最大处理线程数。从以上三个参数的含义,我们可以知道以下结论:客户端并不直接与Tomcat的Connector组件建立联系,而是先与操作系统建立联系,再交给Connector。这个很重要,不然你是看不懂acceptCount这个参数的。不仅Connector组件中有一个队列,操作系统中也有一个队列来临时存放与客户端的连接,这也是一个关键点。我们所说的线程池是指Container容器中的线程池。理解这三个核心参数的含义非常重要,否则就没有办法进行后续的性能调优工作。maxThreads我们知道maxThreads是指最大请求处理线程数,在Tomcat7和Tomcat8中默认都是200。该参数的设置需要根据任务的执行内容进行调整。一般来说,计算公式为:最大线程数=((IO时间+CPU时间)/CPU时间)*CPU核数。这个公式的思路其实很简单,就是最大化利用CPU资源。任务的耗时分为IO耗时和CPU耗时。基本上IO时间消耗是最多的,此时CPU无事可做。所以如果在任务等待IO的时候可以让CPU去处理其他的任务,那么CPU的利用率就会提高。一般来说,由于IO耗时远大于CPU耗时,所以根据公式计算出来的maxThreads个数会远大于CPU核数,这是正常的。需要注意的是,这个值并不是越高越好。因为一旦线程过多,CPU就需要进行上下文切换,会消耗一部分CPU资源。因此,最好的办法就是用上面的公式计算出一个基准值,然后进行压力测试,调整到一个合理的值。一般来说,如果增加了maxThreads的值,但吞吐量没有增加或减少,则可能表明已经达到了瓶颈。maxConnectionsmaxConnections指的是当线程池中的线程达到最大值并且都处于忙碌状态时,Connector中的队列可以容纳的最大连接数。一般来说,我们要设定一个合理的值,不能让它无限制地堆起来。因为Tomcat的处理能力肯定是有限的,到了一定程度就肯定处理不了了。所以积累多了也没用。反而会造成内存堆积,最终导致内存溢出OOM。一般来说,经验值可以设置为与maxThreads相同的大小。我觉得这样比较合理,因为队列中的连接最多只需要等待线程处理一个任务,不会等待太久,响应时间也不会太长。如果想缩短响应时间,可以将maxConnections调得比maxThreads低,这样可以减少一些响应时间。但需要注意的是,如果降得太低,性能可能会严重下降,吞吐量也会降低。acceptCountacceptCount是指当Container线程池达到最大数且没有空闲线程,Connector队列达到最大数时,操作系统可以接受的最大连接数。当队列中的数量达到最大值时,将拒绝所有传入的请求。默认值为100,这个可以理解为操作系统的一种自我保护机制。如果积累太多无法处理,那就拒绝它们以保护自己的资源。这个参数的调优资料比较少,但是根据它的意思,不建议这个值大于maxConnections。因为这个队列中的连接需要等待。如果这个值太大,意味着会有很多连接没有被处理。连接越多,等待的时间越长,响应时间越慢。如果你想要更短的响应时间,你应该降低这个值。有的同学会疑惑,为什么我们有maxConnections还要acceptCount呢?这不是重蹈覆辙吗?其实在BIO时代,这两个值基本一致。我猜是因为后来NIO、AIO等技术的出现,操作系统可以接受更多的客户端连接。因此,操作系统可以先建立一个连接缓存,然后Connector可以直接从操作系统获取连接,这样就不需要等待操作系统进行耗时的TCP连接,从而提高效率.除了以上三个参数外,还有几个非核心参数,但我觉得还是有一定作用的。connectionTimeout参数表示连接建立后的等待超时时间。如果超过这个时间,则直接返回超时。minSpareThreads参数表示最小存活线程数,即如果没有请求,那么必须保持最小存活线程数。该参数与是否有突发流量相关联。在突发流量的情况下,如果该值过低,瞬时响应时间会比较长。总结今天我们分享了Tomcat的核心组件,然后讲解了Tomcat在处理请求时的三个核心参数和调优经验。对于maxThreads参数,如果按照公式计算,我们需要获取IO时间和CPU时间,但实际上这两个值并不容易获取。所以一般情况下,我们可以通过压测得到一个比较合适的maxThreads。对于maxConnections参数,可以设置一个和maxThreads一样的值,然后根据具体情况进行调整。如果你想减少响应时间,你可以稍微调低它,否则你可以调高它。对于acceptCount参数,其调优逻辑类似于maxConnections,可以类似maxConnections设置,然后根据相应的时间要求进行微调。