
1. 项目概述从零构建一个“五脏俱全”的性能测试环境每次接手一个新项目或者要对现有系统做一次深度的性能摸底最头疼的是什么不是写测试脚本也不是分析报告而是搭建那个“该死”的测试环境。开发扔给你一个项目说“你测一下性能”结果你发现连个能跑起来的、接近生产环境的测试服务器都没有。自己动手搭JDK版本不对、Tomcat配置冲突、Redis连不上、MySQL字符集报错……随便一个坑就能耗掉你大半天。这活儿干多了我算是明白了一个稳定、可控、可复现的性能测试环境本身就是性能测试成功的一半。今天要聊的就是怎么把这“四件套”——JDK、Tomcat、Redis、MySQL——给整整齐齐地凑到一台机器上搭出一个专为性能测试服务的“沙盒”。这不仅仅是安装软件更是为后续的压测铺平道路。比如你得确保Tomcat能吃到足够的内存以模拟生产环境的JVM配置Redis的持久化策略得调整避免压测时磁盘IO成为瓶颈MySQL的慢查询日志和性能模式必须打开方便我们抓取SQL瓶颈。这个环境搭建好了无论是用JMeter、Gatling还是自己写的脚本去压你得到的数据才是可信的排查问题才有依据。这个教程适合谁呢如果你是刚入行的测试工程师对后端组件还比较陌生那这篇可以当作一份详细的部署手册。如果你是有经验的性能测试人员但每次搭环境还是靠“祖传”的文档和运气那这里面的配置思路和避坑经验或许能给你一些新启发。我们的目标很明确在Linux服务器上以CentOS 7为例搭建一个配置清晰、便于监控、可重复使用的性能测试基础环境。2. 环境规划与核心组件选型考量在动手敲命令之前花十分钟做一下规划绝对能省下后面两小时排错的时间。性能测试环境不是软件装上去就完事了你得考虑资源分配、版本兼容性和后续的监控便利性。2.1 服务器资源规划首先别想着用自己那台8G内存的笔记本虚拟机来模拟线上8核32G的服务。资源不足会导致测试结果严重失真。建议至少准备一台4核8G及以上的云服务器或物理机。这里我们做一个基础规划系统CentOS 7.9 Minimal。选择它是因为其稳定性高资料丰富且是很多企业的实际生产选择。目录规划混乱的安装目录是噩梦的开始。我习惯创建一个/opt/bench目录作为我们性能测试环境的“家目录”。mkdir -p /opt/bench/{java,tomcat,redis,mysql,logs,scripts}java、tomcat、redis、mysql分别存放各自的安装包和解压后的软件。logs集中存放各组件及压测工具产生的日志。scripts存放一些自动化启停、监控脚本。网络与防火墙确保服务器内部环路127.0.0.1畅通并根据需要开放对外端口如Tomcat的8080MySQL的3306。如果只是内网压测可以暂时关闭防火墙或配置精确规则避免网络成为干扰项。# 临时关闭防火墙测试环境常用生产环境慎用 systemctl stop firewalld systemctl disable firewalld # 或者更优雅地只开放需要的端口 # firewall-cmd --zonepublic --add-port8080/tcp --permanent # firewall-cmd --reload2.2 组件版本选型背后的逻辑版本不能随便选要尽量贴近生产环境同时考虑稳定性和社区支持。JDK选择JDK 8 (LTS)或JDK 11 (LTS)。目前绝大多数企业级应用仍运行在JDK 8上它的稳定性和工具链最成熟。如果你们公司已经升级就用JDK 11。绝对不要在性能测试环境使用最新的非LTS版本如JDK 17刚发布时避免因JDK本身的不稳定影响测试结果。这里我们选择Oracle JDK 8u202需官网下载或OpenJDK 8。我倾向于OpenJDK避免授权问题。Tomcat选择Tomcat 8.5.x或9.0.x。同样需要与生产环境对齐。Tomcat 9在HTTP/2等方面有改进但8.5足够稳定且广泛使用。我们将使用Tomcat 9.0.56作为示例。Redis选择Redis 6.x。Redis 6引入了多线程IO处理网络请求命令执行仍是单线程性能有显著提升并且增加了ACL等安全特性。对于性能测试环境6.x是一个兼具新特性和稳定性的好选择。我们将使用Redis 6.2.6。MySQL选择MySQL 5.7或8.0。MySQL 5.7仍然是许多公司的标配其性能表现和运维经验非常丰富。8.0在性能如直方图、JSON支持和默认字符集utf8mb4上有很大改进。如果生产环境是5.7测试环境就用5.7。这里我们选择MySQL 8.0.28。注意8.0的默认身份认证插件从mysql_native_password改为了caching_sha2_password一些老的客户端可能不支持需要留意。注意所有安装包建议提前下载好上传到服务器的/opt/bench目录下或者使用wget直接从国内镜像站下载避免安装过程中因网络问题中断。3. 分步部署安装与基础配置实战规划好了我们就开始一步步安装配置。请跟着操作并理解每一步的意图。3.1 JDK安装与环境变量配置JDK是Java应用的运行基石配置不对后面全白费。上传与解压假设我们已经将jdk-8u202-linux-x64.tar.gz上传到了/opt/bench。cd /opt/bench tar -zxvf jdk-8u202-linux-x64.tar.gz -C java/解压后路径为/opt/bench/java/jdk1.8.0_202。配置环境变量这是关键一步。修改全局配置文件/etc/profile在文件末尾添加export JAVA_HOME/opt/bench/java/jdk1.8.0_202 export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH$PATH:$JAVA_HOME/bin为什么是/etc/profile因为它对所有用户生效避免切换用户后找不到java命令。你也可以选择配置在~/.bashrc中但那样只对当前用户有效。生效与验证source /etc/profile # 使配置立即生效 java -version如果看到类似“java version “1.8.0_202””的输出说明安装成功。实操心得经常有人配置后java -version还是显示系统自带的OpenJDK老版本。这通常是PATH变量顺序问题。echo $PATH查看确保$JAVA_HOME/bin的路径在系统默认路径之前。或者更彻底一点用alternatives --config java如果系统装了alternatives来选择默认Java版本。3.2 Tomcat部署与性能调优配置Tomcat是我们Web应用的容器它的配置直接影响应用承载并发的能力。上传与解压cd /opt/bench tar -zxvf apache-tomcat-9.0.56.tar.gz -C tomcat/得到目录/opt/bench/tomcat/apache-tomcat-9.0.56。为了方便可以创建一个软链接ln -s /opt/bench/tomcat/apache-tomcat-9.0.56 /opt/bench/tomcat/current这样以后操作/opt/bench/tomcat/current即可升级时只需更换软链接目标。关键性能配置修改主要修改/opt/bench/tomcat/current/conf/server.xml。连接器(Connector)优化找到默认的HTTP/1.1 Connector配置端口8080进行如下修改Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads500 !-- 最大线程数根据机器配置调整通常200-800 -- minSpareThreads50 !-- 最小空闲线程 -- acceptCount500 !-- 等待队列长度当线程用完时请求在此排队 -- maxConnections10000 !-- 最大连接数 -- compressionon !-- 开启GZIP压缩减少网络传输量 -- compressionMinSize1024 compressableMimeTypetext/html,text/xml,text/plain,text/css,text/javascript,application/json /maxThreads是核心参数它决定了Tomcat处理请求的并发能力。设置太低会成为瓶颈太高会导致线程切换开销巨大。一般从200开始根据压测结果调整。关闭AJP连接器如果不用Apache和Tomcat做集群找到Connector port8009 protocolAJP/1.3 ...这一行注释掉它可以减少一个端口的占用和潜在的安全风险。JVM内存参数配置修改/opt/bench/tomcat/current/bin/catalina.shLinux或catalina.batWindows。在文件开头注释之后添加export JAVA_OPTS-server -Xms2048m -Xmx2048m -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m -XX:UseG1GC -XX:PrintGCDetails -XX:PrintGCDateStamps -Xloggc:/opt/bench/logs/gc.log-Xms2048m -Xmx2048m设置堆内存初始值和最大值都为2G避免运行中动态调整带来的性能波动。-XX:UseG1GC使用G1垃圾收集器在大多数场景下比传统的Parallel或CMS收集器有更好的延迟表现尤其适合大内存堆。-Xloggc将GC日志输出到指定文件这是性能测试的黄金数据用于分析GC频率和停顿时间。启动与验证cd /opt/bench/tomcat/current/bin ./startup.sh tail -f ../logs/catalina.out # 查看启动日志浏览器访问http://服务器IP:8080看到Tomcat欢迎页即成功。3.3 Redis安装与内存优化配置Redis作为缓存它的响应速度直接关系到应用接口的TPS。安装依赖与编译安装Redis通常从源码编译安装以获得最佳性能和控制。cd /opt/bench yum install -y gcc-c # 安装编译工具 tar -zxvf redis-6.2.6.tar.gz -C redis/ cd redis/redis-6.2.6 make make PREFIX/opt/bench/redis/current install # 指定安装目录关键性能与持久化配置复制默认配置文件并修改。mkdir -p /opt/bench/redis/conf /opt/bench/redis/data /opt/bench/redis/logs cp /opt/bench/redis/redis-6.2.6/redis.conf /opt/bench/redis/conf/ vi /opt/bench/redis/conf/redis.conf需要修改以下几项bind 127.0.0.1 # 只允许本机连接安全。如果压测机与应用分离需改成服务器IP或0.0.0.0不安全仅测试 protected-mode no # 如果bind不是127.0.0.1需要关闭保护模式 port 6379 daemonize yes # 以守护进程运行 pidfile /opt/bench/redis/redis.pid logfile /opt/bench/redis/logs/redis.log dir /opt/bench/redis/data # 持久化文件存放目录 # 内存策略根据测试需求调整。如果只是临时缓存可以设置最大内存和淘汰策略。 maxmemory 1gb # 设置最大内存为1GB防止Redis吃光所有内存 maxmemory-policy allkeys-lru # 内存满时删除最近最少使用的键 # 持久化策略性能测试时为了追求极致速度可以暂时关闭或调整。 # save 900 1 # 注释掉默认的RDB持久化规则避免测试时频繁fork子进程造成延迟抖动 # save 300 10 # save 60 10000 appendonly no # 关闭AOF持久化。如果测试需要数据持久化可以开启但会牺牲一些写入性能。启动与测试/opt/bench/redis/current/bin/redis-server /opt/bench/redis/conf/redis.conf /opt/bench/redis/current/bin/redis-cli ping # 应返回 PONG注意事项在性能测试期间如果你不关心Redis数据的持久化强烈建议关闭RDB和AOF。因为bgsave或AOF重写会fork进程在数据量大时可能导致服务短暂停顿影响压测数据的准确性。测试完成后再根据需求调整持久化配置。3.4 MySQL安装与数据库初始化MySQL是数据的最终归宿它的配置决定了数据操作的效率。卸载MariaDB并安装MySQLCentOS 7默认带有MariaDB需要先移除。rpm -qa | grep mariadb sudo rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64 # 根据查询到的包名卸载 cd /opt/bench # 从MySQL官网或国内镜像下载RPM Bundle例如 mysql-8.0.28-1.el7.x86_64.rpm-bundle.tar tar -xvf mysql-8.0.28-1.el7.x86_64.rpm-bundle.tar sudo rpm -ivh mysql-community-common-8.0.28-1.el7.x86_64.rpm \ mysql-community-client-plugins-8.0.28-1.el7.x86_64.rpm \ mysql-community-libs-8.0.28-1.el7.x86_64.rpm \ mysql-community-client-8.0.28-1.el7.x86_64.rpm \ mysql-community-icu-data-files-8.0.28-1.el7.x86_64.rpm \ mysql-community-server-8.0.28-1.el7.x86_64.rpm初始化数据库与修改密码sudo mysqld --initialize --usermysql --basedir/usr --datadir/var/lib/mysql # 注意初始化命令会在日志中生成一个临时root密码务必记下来。 sudo grep temporary password /var/log/mysqld.log sudo systemctl start mysqld sudo systemctl enable mysqld mysql -uroot -p # 输入刚才记下的临时密码登录后立即修改密码并创建测试用的数据库和用户ALTER USER rootlocalhost IDENTIFIED BY YourNewStrongPassword!123; -- 创建一个用于性能测试的数据库和用户更安全 CREATE DATABASE bench_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER bench_user% IDENTIFIED BY BenchUserPass!456; GRANT ALL PRIVILEGES ON bench_db.* TO bench_user%; FLUSH PRIVILEGES;关键性能配置修改MySQL配置文件/etc/my.cnf在[mysqld]段添加[mysqld] # 连接相关 max_connections500 # 最大连接数根据压测并发量调整 thread_cache_size100 # 线程缓存大小 # InnoDB存储引擎优化核心 innodb_buffer_pool_size1G # InnoDB缓冲池大小设置为系统内存的50%-70%这里假设机器4G给1G innodb_log_file_size256M # 重做日志文件大小增大可减少刷盘频率 innodb_flush_log_at_trx_commit2 # 设置为2每秒刷一次日志到磁盘性能与安全的折中测试环境可设 innodb_flush_methodO_DIRECT # 避免双重缓冲提升I/O效率 # 查询缓存MySQL 8.0已移除如果是5.7可以考虑关闭因为在高并发下其锁开销可能更大 # query_cache_type0 # query_cache_size0 # 慢查询日志 - 性能测试必备 slow_query_log1 slow_query_log_file/var/lib/mysql/slow-query.log long_query_time1 # 超过1秒的查询记录 # 性能模式Performance Schema- 用于深度性能分析 performance_schemaON修改配置后重启MySQLsudo systemctl restart mysqld4. 集成验证与性能测试准备组件都装好了现在需要验证它们是否能协同工作并为正式的压测做好准备。4.1 部署一个简单的测试应用我们写一个最简单的Spring Boot Web应用它同时调用Redis和MySQL用来验证环境和作为压测目标。创建项目使用Spring Initializr或IDE创建一个Spring Boot项目依赖选择Web, JDBC API, MySQL Driver, Spring Data Redis (Lettuce)。编写一个测试ControllerRestController public class BenchController { Autowired private StringRedisTemplate redisTemplate; Autowired private JdbcTemplate jdbcTemplate; GetMapping(/bench) public MapString, String benchTest() { MapString, String result new HashMap(); // 1. 测试Redis String key bench:key; String redisValue Hello from Redis at System.currentTimeMillis(); redisTemplate.opsForValue().set(key, redisValue, 1, TimeUnit.MINUTES); result.put(redis_read, redisTemplate.opsForValue().get(key)); // 2. 测试MySQL String sql SELECT Hello from MySQL as message; String mysqlResult jdbcTemplate.queryForObject(sql, String.class); result.put(mysql_read, mysqlResult); // 3. 模拟一个耗时操作 try { Thread.sleep(new Random().nextInt(50)); // 随机睡眠0-50毫秒 } catch (InterruptedException e) { e.printStackTrace(); } result.put(status, success); return result; } }配置application.propertiesserver.port8080 spring.datasource.urljdbc:mysql://localhost:3306/bench_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai spring.datasource.usernamebench_user spring.datasource.passwordBenchUserPass!456 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver spring.redis.hostlocalhost spring.redis.port6379 spring.redis.lettuce.pool.max-active8 spring.redis.lettuce.pool.max-idle8打包与部署将项目打成war或可执行的jar包。这里以jar为例放到Tomcat的webapps目录下并不合适。更简单的方式是直接用Spring Boot内嵌的Tomcat运行。# 在项目根目录下 mvn clean package -DskipTests # 将生成的 target/bench-test-app-0.0.1-SNAPSHOT.jar 上传到服务器例如 /opt/bench/app/ cd /opt/bench/app nohup java -jar -Xms512m -Xmx512m bench-test-app-0.0.1-SNAPSHOT.jar --server.port8081 app.log 21 这样我们的测试应用就在8081端口独立运行了。访问http://服务器IP:8081/bench应该能看到包含Redis和MySQL返回信息的JSON。4.2 配置系统监控基线在开始压测前我们需要知道系统在“空闲”状态下的资源使用情况作为对比基线。使用top/htop观察CPU、内存的整体使用率。使用vmstat 2每2秒输出一次系统状态关注r运行队列、b阻塞进程、swpd虚拟内存使用、si/so内存交换等列。理想情况下si和so应为0表示没有发生内存交换。使用iostat -x 2查看磁盘I/O状况关注%util设备利用率和await平均等待时间。如果%util持续高于80%说明磁盘可能是瓶颈。监控组件状态JVM用jstat -gcutil pid 2000查看GC情况或用前面配置的GC日志。Redis用redis-cli info命令查看内存、连接数、命令统计等信息。MySQL用SHOW GLOBAL STATUS LIKE ‘Threads_connected’;SHOW ENGINE INNODB STATUS\G等命令查看连接和InnoDB状态。把这些“健康指标”记录下来。压测时一旦发现CPU持续100%、内存耗尽、磁盘await飙升、MySQL连接数爆满或Redis内存溢出问题点就暴露出来了。5. 常见问题与性能瓶颈排查实录环境搭好了测试也跑了但结果不理想或者中途报错怎么办下面是我在实际工作中遇到的一些典型问题及排查思路。5.1 连接类问题问题应用无法连接MySQL报“Access denied for user”或“Public Key Retrieval is not allowed”。排查检查用户名、密码、数据库名是否正确。MySQL 8.0的密码插件问题在JDBC连接串后添加allowPublicKeyRetrievaltrueuseSSLfalse测试环境。检查用户权限SHOW GRANTS FOR bench_user%;确认有从该主机连接的权限。检查MySQL是否绑定了127.0.0.1而非0.0.0.0netstat -tlnp | grep 3306。如果只看到127.0.0.1:3306需要在my.cnf中设置bind-address0.0.0.0注意安全风险并重启。问题压测时出现大量“Cannot get connection, timeout”或Redis连接超时。排查检查连接池配置应用中的数据库连接池如HikariCP和Redis连接池Lettuce的最大连接数是否足够。压测并发数应小于连接池最大连接数。检查服务端最大连接数MySQL的max_connectionsRedis的maxclients默认10000通常够用。检查网络和防火墙使用telnet IP 端口命令测试网络连通性。检查服务器句柄数限制Linux默认的进程打开文件数ulimit -n可能不够需要调整。ulimit -n 65535。5.2 资源瓶颈问题问题压测期间TPS上不去服务器CPU使用率不高但响应时间很长。排查首先看GCjstat -gcutil pid 1000如果FGCFull GC次数频繁增加且每次GC后OU老年代使用率下降不多说明可能存在内存泄漏或堆内存设置过小。调整JVM的-Xmx参数。查看磁盘I/Oiostat -x 1如果%util持续90%以上且await很高说明磁盘是瓶颈。可能是MySQL的慢查询导致大量磁盘读或者是Redis的AOF/RDB持久化导致。考虑使用SSD或优化查询/调整持久化策略。查看数据库慢查询去MySQL的slow-query.log里找执行时间长的SQL用EXPLAIN分析其执行计划优化索引。问题压测初期TPS正常运行几分钟后TPS骤降服务器内存占用持续升高。排查内存泄漏使用jmap -histo:live pid | head -20查看堆内存中对象实例排名看是否有某个类异常增多。或者使用jvisualvm等工具远程连接分析堆转储。Redis内存暴增检查是否有错误的缓存使用模式比如用KEYS *命令会阻塞且返回大量数据或者缓存了过大的对象。使用redis-cli --bigkeys找出大key。MySQL连接未释放检查应用代码中是否在执行数据库操作后没有正确关闭Connection、Statement或ResultSet。连接池中的连接如果因为异常没有归还也会导致连接耗尽。5.3 配置优化问题问题Tomcat在高压下出现线程堆积最终无响应。排查检查server.xml中的maxThreads和acceptCount。如果并发请求超过maxThreads新请求会在acceptCount队列中等待队列满了就会拒绝连接。可以适当调高这两个值但更要关注业务处理是否太慢导致线程无法快速释放。结合JVM线程dump分析jstack pid thread_dump.log查看线程状态是不是有很多线程卡在同一个地方比如等待数据库响应。问题Redis响应时间变长redis-cli info显示used_memory接近maxmemory。排查这就是触发了我们之前配置的maxmemory-policy。当内存用满Redis会开始淘汰key这个操作本身会消耗CPU。需要评估缓存的数据量和淘汰策略是否合理。对于性能测试如果数据可以丢失可以适当调大maxmemory如果需要缓存全部热点数据则要确保内存足够。5.4 一个完整的排查案例接口响应时间毛刺现象使用JMeter以100并发持续压测/bench接口平均响应时间在50ms但监控图表上每隔十几秒就出现一个超过500ms的响应毛刺。排查步骤定位时间点从JMeter的响应时间图表或后端应用日志中精确找到毛刺发生的时间戳。关联分析查看GC日志在对应时间点GC日志中是否发生了Full GC果然发现每次毛刺时间点都对应一次Full GC (Allocation Failure)耗时约400ms。分析JVM参数发现启动参数是-Xms512m -Xmx512m但未指定新生代大小。JVM默认的-XX:NewRatio新生代与老年代比例是2即新生代约170MB。对于频繁创建临时对象的Web应用可能偏小导致对象过早进入老年代引发频繁Full GC。解决方案调整JVM参数显式设置更大的新生代并优化GC策略。# 修改启动参数将新生代设置为堆的1/3并使用G1GC的调优参数 -Xms1024m -Xmx1024m -XX:NewSize350m -XX:MaxNewSize350m -XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:PrintGCDetails -Xloggc:/opt/bench/logs/gc.log验证重启应用再次压测。观察GC日志Full GC频率大幅降低响应时间毛刺消失平均响应时间也略有下降。这个案例告诉我们性能测试不仅仅是看最终的数字更要关注过程的稳定性。一个偶发的、周期性的性能劣化其根源往往在于系统某个组件的固有行为如GC。搭建一个配置得当、监控完善的环境是发现和解决这类问题的前提。