一些收藏读书

接口流量突增,如何做好性能调优?

2022-07-25  本文已影响0人  技术栈

对于提供接口服务的应用来说,很多都是用 SpringBoot 默认的 Servlet 容器 Tomcat。在一开始上线的时候,由于大多数流量较小,我们也并不会为 Tomcat 做专门的参数调整。但随着流量越来越大,应用的各项性能指标越来越差,此时我们大多数都会选择扩容。

除了扩容之外,我们还可以选择对 Tomcat 进行性能调优,从而在不增加成本的情况下提升性能。如果面试官问你,流量突增你们一般怎么做,你只会答扩容可就太差劲了。今天树哥就跟大家简单分享下,如何对 Tomcat 进行简单地性能调优,从而提升应用的性能!

组件架构

要对 Tomcat 进行性能调优,我们需要先了解其组件架构。Tomcat 的组件架构如下图所示:

从上图可以看到,Tomcat 将其业务抽象成了 Server、Service、Connector、Container 等等组件,每个组件都有不同的作用。

到这里,Tomcat 的核心组件基本上讲完了。实际上 Container 组件里还细分了很多组件,其实对业务的抽象,感兴趣的可以继续看看。

可以看到,Host 是虚拟主机的抽象,Context 是应用程序的抽象,Wrapper 是 Servlet 的抽象,而 Engine 则是处理层的抽象。

核心参数

在了解核心参数之前,我们我们需要大致了解一下 Tomcat 对于请求的处理流程。Tomcat 对请求的处理流程如下所示:

上述过程可以用如下示意图来表示:

在上面的示意图中有三个非常关键的核心参数,这几个关键的参数也是性能调优的关键,它们分别是:

  1. acceptCount:当 Container 线程池达到最大数量且没有空闲线程,同时 Connector 队列达到最大数量时,操作系统最多能接受的连接数。
  2. maxConnections:当 Container 线程池达到最大数量且没有空闲线程时,Connector 的队列能接收的最大线程数。
  3. maxThreads: Container 线程池的处理线程的最大数量。

从上面三个参数的含义我们可以知道如下几点结论:

  1. 客户端并不是直接与 Tomcat 的 Connector 组件建立联系的,而是先与操作系统建立,然后再移交给 Connector 的。这点很重要,不然你就无法理解 acceptCount 这个参数。
  2. 不仅仅 Connector 组件中有队列,操作系统中也有队列来临时存储与客户端的连接,这也是很关键的点。
  3. 我们所说的线程池,指的是 Container 这个容器里的线程池。

明白这三个核心参数的含义是非常重要的,不然没有办法进行后续的性能调优工作。

maxThreads

我们知道 maxThreads 指的是请求处理线程的最大数量,在 Tomcat7 和 Tomcat8 中都是默认 200 个。

对于这个参数的设置,需要根据任务的执行内容去调整,一般来说计算公式为:最大线程数 = ((IO时间 + CPU时间)/CPU时间) * CPU 核数。这个公式的思路其实很简单,就是最大化利用 CPU 的资源。一个任务的耗时分为 IO 耗时和 CPU 耗时,基本上 IO 耗时是最多的,这时候 CPU 是没事干的。

因此如果可以让 CPU 在任务等待 IO 的时候处理其他任务,那么 CPU 利用率不就上来了么。一般来说,由于 IO 耗时远大于 CPU 耗时,因此根据公式计算出来的 maxThreads 数都会远大于 CPU 核数,这是很正常的。

要注意的是,这个数值也不是越高越好。因为一旦线程数太多了,CPU 需要进行上下文切换,这就消耗了一部分 CPU 资源。因此最好的办法是用上述公式去计算一个基准值,随后再进行压力测试,去调整到一个合理的值。一般来说,如果调高了 maxThreads 的值,但是吞吐量没有提升或者下降的话,那么表明可能到达了了瓶颈了。

maxConnections

maxConnections 指的是当线程池的线程达到最大值,并且都在忙的时候,Connector 中的队列最多能容纳多少个连接。一般来说,我们都要设置一个合理的数值,不能让其无限制堆积。因为 Tomcat 的处理能力肯定是有限的,到达一定程度肯定就处理不过来了,因此你堆积太多了也没啥用,反而会造成内存堆积,最终导致内存溢出 OOM 的发生。

一般来说,一个经验值是可以设置成为 maxThreads 同样的大小。 我想这样也是比较合理的,因为在队列中的连接最多只需要等待线程处理一个任务的时间即可,不会等待太久,响应时间也不会太长。如果你想缩短响应时间,那么可以将 maxConnections 调低于 maxThreads 一些,这样可以降低一些响应时间。但要注意的是,如果降得太低的话,可能就会严重降低性能,降低吞吐量。

acceptCount

acceptCount 指的是当 Container 线程池达到最大数量且没有空闲线程,同时 Connector 队列达到最大数量时,操作系统最多能接受的连接数。 当队列中的个数达到最大值后,进来的请求一律被拒绝,默认值是 100。这可以理解成是操作系统的一种自我保护机制吧,堆积太多无法处理,那就直接拒绝掉,保护自身资源。

这个参数的调优资料比较少,但根据其含义,这个值不建议比 maxConnections 大。 因为在这个队列中的连接,是需要等待的。如果数值太大,就说明会有很多连接没有被处理。连接越多,那么其等待的时间就越长,其响应时间就越慢。如果你想响应时间短一些,或许应该调低一下这个值。

有同学会疑惑,为啥有了 maxConnections 了还要有 acceptCount 呢?这不是重复了么?其实在 BIO 的时代,这两个数值基本都是相同的。我猜是因为后面出现了 NIO、AIO 等技术,操作系统可以接受更多的客户端连接了。于是就可以先让操作系统先建立连接缓存着,随后 Connnector 直接从操作系统处获取连接即可,这样就不需要等待操作系统进行耗时的 TCP 连接了,从而提高了效率。

除了上面这三个参数之外,还有几个非核心参数,但我觉得还是有些作用的。

总结

今天我们分享了 Tomcat 的核心组件,接着讲解了 Tomcat 处理请求过程时的 3 个核心参数及其调优经验。

对于 maxThreads 参数而言,如果按照公式计算的话,我们需要获取 IO 时间和 CPU 时间,但实际上这两个值并不是很好获取。所以一般情况下,我们可以通过压测的方式来获得一个比较合适的 maxThreads。

对于 maxConnections 参数而言,可以设置一个与 maxThreads 相同的值,再根据具体情况进行调整。如果想降低响应时间,那么可以稍微调低一些,否则可以调高一些。对于 acceptCount 参数而言,其调优逻辑与 maxConnections 类似,可以设置与 maxConnections 相似,再根据对相应时间的要求,做一个微调。

上一篇 下一篇

猜你喜欢

热点阅读