跳至主要內容

分布式压测和项目调优

Cactus li2024年12月26日...大约 16 分钟项目性能优化项目性能优化

本章介绍如何使用Docker 容器来搭建压力测试监控平台。

1. 分布式压测

在使用 JMeter 进行大并发压力测试时,单台机器往往受限于内存、CPU 和网络 I/O,导致服务器压力未达到预期,但压测机的压力已经过大并发生崩溃。

为了解决这一问题,JMeter 提供了分布式压测功能,从而显著提升其负载能力。

单机网络带宽有限,高延时场景下,单机可模拟最大线程数有限。

下图是分布式压测架构:

分布式压测图.drawio
分布式压测图.drawio

需要注意的是,JMeter 分布式压测中,Controller 节点负责协调,Slave 节点负责执行测试。当控制器节点配置 10 个线程,每个线程循环 100 次时,单个控制器会产生 1000 个请求样本。在 Master 启动压测后,每台 Slave 都会执行相同的测试配置,向被测服务发送 1000 次请求。因此,如果使用 3 台 Slave,总共会产生 3000 次请求样本。

搭建 JMeter 分布式压测环境注意事项:

1.1 在 Window 系统中搭建部署 JMeter Master

1.2 Linux 部署 JMeter Salve

下载解压安装包命令

mkdir -p /work/JMeter-slave
wget https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.6.3.tgz
tar -zxvf apache-jmeter-5.6.3.tgz
mv apache-jmeter-5.6.3 ./apache-jmeter-5.6.3-salve

修改rmi配置文件和主机的 hostname

cd apache-jmeter-5.6.3-salve/bin

# 修改ip
vim jmeter-server
# RMI_HOST_DEF=-Djava.rmi.server.hostname=本机ip


# 改端口
vim jmeter.properties
# RMI port to be used by the server (must start rmiregistry with same port)
server_port=1099

# To change the default port (1099) used to access the server:
server.rmi.port=1098

配置关闭server.rmi.ssl

# Set this if you don't want to use SSL for RMI
server.rmi.ssl.disable=true

启动jmeter-server服务

nohup ./jmeter-server > ./jmeter.log 2>&1 &

注意:剩下两台 Slave 也是同样的操作。

1.3 分布式环境配置

3. 容器服务优化

3.1 Tomcat容器调优

Spring Boot 应用性能优化中,嵌入式 Tomcat 的调优是关键环节。

当出现响应时间延迟时,通常是由于 Tomcat 内部的 IO 模型(多线程与网络编程)成为瓶颈,并导致系统异常率升高。为了更好地理解系统并发处理能力,并指导线程池配置,我们可以基于响应时间(RT)和吞吐量(TPS)进行服务端并发线程数的估算,公式为:TPS / (1000ms / RT均值)。

Spring Boot 应用依赖于嵌入式 Tomcat 服务器,默认配置可能存在性能瓶颈。通过对 Tomcat 配置进行适当优化,可以显著提升应用性能。

修改配置如下所示:可以使用外挂配置,也可以修改配置文件application.yml

注意,做了任何修改一定要确认配置生效,否则干的再久也是白搭!

server:
  port: 48080  # 配置服务器监听的端口号为 48080
  max-http-header-size: 10MB  # 配置 HTTP 请求头的最大为 10MB
  tomcat:
    accept-count: 1000  # 配置当所有工作线程都被使用时,Tomcat 可以接受的最大排队请求数
    max-connections: 20000  # 配置服务器允许的最大连接数
    threads:
      max: 500  # 配置最大工作线程数
      min-spare: 100  # 配置最小空闲线程数

management:
  endpoints:
    web:
      exposure:
        include: '*'  # 配置暴露所有管理端点
      base-path: /actuator  # 配置管理端点的基本路径为 /actuator
  endpoint:
    shutdown:
      enabled: true  # 允许通过 /actuator/shutdown 来关闭应用程序

那么最大线程数的值应该设置多少合适呢?

个人经验:

  • 1C2G,线程数200

  • 4C8G,线程数800

  • 8C16G,线程数1600

3.1.1 确认调优配置生效

访问:http://218.249.73.244:48080/actuator/configprops

image-20250120155625524
image-20250120155625524

3.1.2 调优前后的性能对比

调优前:压力机Active,RT、TPS、系统进程运行状态【应用活动线程数】

在调整完Spring boot 配置后,使用 Jmeter 压测时可能出现:java.net.BindException:Address already in use. 错误。

出现错误原因:Windows提供给TCPIP连接的端口为1024-5000,并且要四分钟左右循环回收,这就导致我们短时间内频繁调用大量请求时,端口将被占满。

解决这个错误方案 :

按照以上配置后压测项目还会出现 java.net.BindException:Address already in use. 错误。这时就需要调整 Ramp-up period 参数。

image-20250208165241362
image-20250208165241362

可以根据实际情况调整:

基础方案(温和启动):

激进方案(快速启动):

保守方案(缓慢启动):

我的建议是:

  1. 先用保守方案(180秒)测试一轮
  2. 观察系统响应情况(TPS、响应时间、错误率)
  3. 如果系统表现稳定,可以逐步降低到基础方案(60秒)
  4. 重点关注以下指标:
    • 响应时间是否稳定
    • 错误率是否在可接受范围内
    • 系统资源使用情况

如果你的系统能够很好地处理基础方案,而且需要测试更极限的情况,才考虑使用激进方案。

记住:较短的 Ramp-up period 会产生更大的突发压力,可能导致更多的连接错误。建议从较大的值开始,然后根据实际情况逐步调整。

**推荐:**把 jmmeter 程序迁移到一台安装程序较少比较干净的系统内进行压力测试。这样也可以解决这个问题!

调优后:压力机Active,RT、TPS、系统进程运行状态【应用活动线程数】

image-20250212103139395
image-20250212103139395
image-20250212103935128
image-20250212103935128

优化结论:提升Tomcat最大线程数,在高负载场景下,TPS提升接近1倍多,同时RT大幅降低;

3.2 网络IO模型调优

3.2.1 IO 模型介绍

新旧IO比较在Java 程序中文件读写性能是影响应用程序性能的关键因素之一,NIO与原来的IO有同样的作用和目的,但 是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作,NIO以更加高效的方式进行文件 的读写操作。

在Java 中NIO是从Java 1.4版本开始引入的一套新的IO API用于替代标准的Java IO API。

JDK1.7之后,Java对NIO再次进行了极大的改进,增强了对文件处理和文件系统特性的支持。我们称之 为AIO,也可以叫NIO2。

优化 Spring Boot 2.7.x 内置 Tomcat 9.x 配置,使用NIO2的Http协议实现,对请求连接器进行改写!

调整后的配置:

@Configuration
public class TomcatConfig {
	/**
     * 启用 NIO2(Http11Nio2Protocol)协议
     *
     * @return
     */
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
        return factory -> {
            factory.setProtocol("org.apache.coyote.http11.Http11Nio2Protocol");
            factory.addConnectorCustomizers(this::http11Nio2Connector);
        };
    }

    private void http11Nio2Connector(Connector connector) {
        Http11Nio2Protocol nio2Protocol = (Http11Nio2Protocol)
                connector.getProtocolHandler();
        //等待队列最多允许1000个线程在队列中等待
        nio2Protocol.setAcceptCount(1000);
        // 设置最大线程数
        nio2Protocol.setMaxThreads(500);
        // 设置最大连接数
        nio2Protocol.setMaxConnections(10000);
        //定制化keepalivetimeout,设置30秒内没有请求则服务端自动断开keepalive链接
        nio2Protocol.setKeepAliveTimeout(30000);
        //当客户端发送超过10000个请求则自动断开keepalive链接
        nio2Protocol.setMaxKeepAliveRequests(10000);
        // 请求方式
        connector.setScheme("http");
        connector.setPort(48080); //自定义的端口,与源端口9001
        connector.setRedirectPort(8443);
    }
}

3.2.2 调优前后的性能对比

没有使用NIO2的Http协议实现之前的 RT、TPS

image-20250212103139395
image-20250212103139395
image-20250214094940158
image-20250214094940158
image-20250214095046933
image-20250214095046933
image-20250218130541053
image-20250218130541053

使用NIO2的Http协议实现之后的 RT、TPS

image-20250214104446889
image-20250214104446889
image-20250214104623856
image-20250214104623856
image-20250214104641451
image-20250214104641451
image-20250214104823601
image-20250214104823601

从以上图片信息的对比来看,使用NIONIO2后,主要的提升体现在以下几个方面:

总的来说,NIO2在性能上表现出了更加稳定的响应时间和吞吐量,尤其是在高线程情况下,系统处理更多请求时的效率得到了明显提升。

3.3 内置Tomcat升级成Undertow

官网https://undertow.io/

Tomcat是Apache开源的一个轻量级的Servlet容器。Tomcat具有Web服务器特有的功能,包括 Tomcat管理和控制平台、安全局管理和Tomcat阀等。Tomcat本身包含了HTTP服务器,因此也可以视作单独的Web服务器。但是,Tomcat和ApacheHTTP服务器不是一个东西,ApacheHTTP服务器是用C语言实现的HTTP Web服务器。Tomcat是完全免费的,深受开发者的喜爱。

Undertow是Red Hat公司的开源产品, 它完全采用Java语言开发,是一款灵活的高性能Web服务器,支持阻塞IO和非阻塞IO。由于Undertow采用Java语言开发,可以直接嵌入到Java项目中使用。同时, Undertow完全支持Servlet和Web Socket,在高并发情况下表现非常出色。

3.3.1 具体配置过程

  1. 排除tomcat 在spring-boot-starter-web中

    <!-- 排除 Spring Boot 默认的 Tomcat -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  2. 导入 starter-undertow

    <!-- 添加 Undertow 作为 Web 服务器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
  3. 配置 application.yml

    server:
      port: 48080
      max-http-header-size: 10MB # 最大 HTTP 头大小,默认 8KB
      undertow:
        threads:
          io: 500 # IO线程数量,这里设置为800。这些线程专门用于处理网络I/O操作,如读取和写入数据,提高了并发处理能力
          worker: 5000 # 默认值是IO线程数*8
        buffer-size: 1024 # 设置Undertow使用的缓冲区大小,这里是1024字节(1KB)。缓冲区大小影响数据传输的效率,较大的缓冲区可以减少I/O操作的次数,但也会增加内存使用
        direct-buffers: true # 是否使用直接缓冲区,这里设置为true。直接缓冲区可以减少内存拷贝操作,提高性能,但需要更多的内存管理,因为它们不受垃圾回收的影响

3.3.2 升级优化前后的性能对比

优化前:RT、TPS ,注意下图是没有配置NIO2优化的Tomcat。

image-20250218152340445
image-20250218152340445
image-20250218152513403
image-20250218152513403
image-20250218152620075
image-20250218152620075

调优后:RT、TPS

image-20250218161952123
image-20250218161952123
image-20250218162017567
image-20250218162017567
image-20250218162036170
image-20250218162036170

根据报告总结:

此外,还有一些优化点:

这些优化方式可以根据实际情况进行选择,例如我们通过分析应用性能瓶颈,找到关键的优化点,以达到最佳的性能表现。虽然无法保证完全优化,但可以显著提升性能。

4. 数据库调优

4.1 为什么要数据库调优?

因为程序的响应时间,是受数据库读写操作影响的。

提升网站整体通吐量,优化用户体验数据库是关键性能之一。

总的来说,数据库调优是为了提高查询速度、节省资源、提升用户体验、降低成本、支持业务增长和解决性能瓶颈,确保数据库系统高效运行。

4.2 什么影响数据库性能?

4.3 数据库调优到底调什么?

为什么使用索引就能加快查询速度呢?

列举出来算法的时间复杂度有部分,可能感知不到差距。

假设表中有100w条数据,如果是根据id查找,一般顺序查找平均需要找50万条数据,对比50万次才能找到。如果采用索引二分法查找,最多不超过20次对比即可找到。

可以的看出,两种方式的执行效率相差2.5万倍.

5. OpenResty 调优

OpenResty 简单介绍

官网https://openresty.org/en/installation.html

OpenResty 是一个基于 Nginx 的 Web 平台,用于构建高效的 Web 应用程序,API 和网关。其内部集成了大量精良的 Lua 库、第三方 模块以及大多数的依赖项。

OpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻 塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

关于Nginx 是什么?

Nginx 是一个开源的高性能HTTP服务器和反向代理服务器,同时也能够作为IMAP/POP3代理服务器。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中 表现较好。

5.1 使用 Docker 安装 OpenResty

拉取最新镜像:

docker pull openresty/openresty:latest

要特定的版本,比如固定版本1.19.9.1,可以这样拉取:

docker pull openresty/openresty:1.19.9.1-4-alpine

alpine 标签表示基于Alpine Linux的镜像,通常体积较小。

创建openresty需要挂载的目录文件:

mkdir -p /sg-work/openresty/nginx/
mkdir -p /sg-work/openresty/conf/
mkdir -p /sg-work/openresty/luascript/

启动容器并挂载目录:

docker run -d --name my-openresty  -p 8456:80     openresty/openresty:latest

docker cp my-openresty:/usr/local/openresty/nginx/conf/nginx.conf /sg-work/openresty/nginx/conf/
docker cp my-openresty:/etc/nginx/conf.d /sg-work/openresty/nginx/conf/
docker cp my-openresty:/usr/local/openresty/nginx/logs /sg-work/openresty/nginx/
docker cp my-openresty:/usr/local/openresty/nginx/html /sg-work/openresty/nginx/


docker run -d --name my-openresty \
    -p 8456:80 \
    -v /sg-work/openresty/nginx/conf:/usr/local/openresty/nginx/conf/nginx.conf \
    -v /sg-work/openresty/nginx/conf/conf.d:/etc/nginx/conf.d \
    -v /sg-work/openresty/nginx/logs:/usr/local/openresty/nginx/logs \
    -v /sg-work/openresty/nginx/html:/usr/local/openresty/nginx/html \
    openresty/openresty:latest

docker run -d --name my-openresty \
    -p 80:80 \
    -p 443:443 \
    -p 8786:8786 \
    -p 8987:8987 \
    -v  /sg-work/openresty/nginx/conf:/usr/local/openresty/nginx/conf \
    -v  /sg-work/openresty/nginx/html:/usr/local/openresty/nginx/html \
    -v  /sg-work/openresty/nginx/logs:/usr/local/openresty/nginx/logs \
    -v  /sg-work/openresty/conf:/etc/nginx/conf.d \
    -v  /sg-work/openresty/luascript:/usr/local/openresty/luascript \
    openresty/openresty:latest

6. 缓存调优

7. JVM调优