1. 服务器性能优化的核心指标解析
当服务器负载飙升时,最直观的表现就是响应变慢甚至服务不可用。要解决这个问题,我们首先需要理解三个关键性能指标:CPU、内存和磁盘I/O。这就像医生看病要先量血压、测心跳一样,服务器诊断也要从这些基础指标入手。
CPU使用率是判断计算资源是否过载的首要指标。正常情况下,CPU使用率应该在70%以下,如果长期超过90%,就说明计算资源吃紧。我遇到过最极端的情况是某个Java应用因为线程死锁导致CPU飙到100%,整个系统直接卡死。这时候用top命令就能快速定位问题进程:
top -c内存方面主要看两个指标:使用量和交换分区(swap)情况。物理内存不足时系统会开始使用swap,但swap的性能比物理内存差很多,一旦开始频繁交换,性能就会断崖式下降。可以用free -h查看内存状态:
free -h total used free shared buff/cache available Mem: 62G 25G 3.2G 1.2G 33G 35G Swap: 8.0G 512M 7.5G磁盘I/O问题往往最难排查。我曾经处理过一个MySQL查询变慢的问题,最后发现是因为磁盘队列长度(await)太高。使用iostat可以查看磁盘负载:
iostat -x 1 Device r/s w/s rkB/s wkB/s await svctm %util sda 5.00 20.00 320.00 1280.00 12.00 4.00 10.002. CPU性能优化实战
2.1 压力测试工具的选择与使用
要对CPU进行压力测试,我推荐使用sysbench这个多线程基准测试工具。它不仅可以测试CPU性能,还能测试内存、磁盘和数据库性能。安装很简单:
# Ubuntu/Debian sudo apt install sysbench # CentOS/RHEL sudo yum install sysbench测试CPU性能的命令如下,这里用40个线程计算质数到20000:
sysbench cpu --threads=40 --cpu-max-prime=20000 run测试结果中要特别关注"events per second"这个指标,数值越高说明CPU性能越好。在我的测试中,AMD EPYC 7763处理器的成绩是Intel Xeon 8380的1.3倍左右。
2.2 代码层面的CPU优化
在实际项目中,我见过太多因为代码不当导致CPU使用率飙升的案例。有几点经验特别值得分享:
第一,避免不必要的计算。比如有个电商系统在计算商品价格时,每次都要重新计算税费和折扣,后来改为只在价格变动时计算,CPU负载直接降了30%。
第二,合理使用缓存。我曾经优化过一个天气预报服务,把计算结果缓存5分钟,QPS从100提升到了2000+。用Java实现很简单:
// 使用Guava Cache LoadingCache<String, WeatherData> cache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(new CacheLoader<String, WeatherData>() { public WeatherData load(String city) { return calculateWeather(city); } });第三,选择合适的数据结构。HashMap查找是O(1),而ArrayList的contains是O(n),在大数据量时差异巨大。我曾经把一个使用ArrayList.contains()的代码改为HashSet.contains(),执行时间从3秒降到了0.01秒。
3. 内存优化深度解析
3.1 内存泄漏排查实战
内存泄漏是Java应用的常见问题。有一次我们的服务运行几天后就会OOM,用jmap和jvisualvm分析后发现是某个静态Map不断增长导致的。排查步骤很经典:
- 获取堆转储文件:
jmap -dump:format=b,file=heap.hprof <pid>用MAT或jvisualvm分析,发现某个Map占了80%内存
检查代码发现是缓存没有设置上限和过期时间
最终解决方案是改用Caffeine缓存,设置最大条目和过期时间:
Cache<String, Data> cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(1, TimeUnit.HOURS) .build();3.2 内存分配优化技巧
JVM内存配置对性能影响巨大。经过多次调优,我总结出一个经验公式:对于8G物理内存的机器,可以这样配置:
-Xms6g -Xmx6g -XX:MaxMetaspaceSize=256m -XX:ReservedCodeCacheSize=128m解释一下:
- Xms和Xmx设为相同值避免动态调整开销
- 留2G给操作系统和其他进程
- Metaspace和CodeCache根据应用类型调整
对于Tomcat,还需要注意线程数设置。太多线程会导致内存不足,太少又无法充分利用CPU。经验值是:
# 在setenv.sh中设置 export CATALINA_OPTS="-Xmx4g -Xms4g -XX:MaxMetaspaceSize=256m" # 在server.xml中设置 <Connector port="8080" maxThreads="200" minSpareThreads="20"/>4. 磁盘I/O性能调优
4.1 文件系统选择与优化
磁盘性能调优首先要选对文件系统。经过多次测试,我发现:
- 对于SSD,ext4和xfs性能相当
- 对于HDD,xfs性能更好
- 对于数据库,建议用xfs并设置noatime
挂载时可以这样优化:
# /etc/fstab /dev/sdb1 /data xfs defaults,noatime,nodiratime 0 0对于MySQL这类数据库,还要调整I/O调度器。SSD建议用noop或deadline:
echo noop > /sys/block/sdb/queue/scheduler4.2 数据库I/O优化
MySQL的I/O优化我总结为"三板斧":
第一,合理设置innodb_buffer_pool_size,这个值应该占可用内存的70-80%:
[mysqld] innodb_buffer_pool_size=12G第二,调整刷盘策略。对于允许少量数据丢失的场景,可以这样设置:
innodb_flush_log_at_trx_commit=2 sync_binlog=1000第三,使用SSD并开启O_DIRECT:
innodb_flush_method=O_DIRECT我曾经用这些优化方法,把一个每秒只能处理200请求的MySQL提升到了2000+。
5. 全链路压力测试方案
5.1 测试工具链搭建
完整的压力测试需要一套工具链。我的常用组合是:
- JMeter:模拟用户请求
- Prometheus + Grafana:监控系统指标
- ELK:收集和分析日志
一个简单的JMeter测试计划可以这样配置:
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="HTTP测试" enabled="true"> <intProp name="ThreadGroup.num_threads">100</intProp> <intProp name="ThreadGroup.ramp_time">60</intProp> <longProp name="ThreadGroup.duration">300</longProp> </ThreadGroup>5.2 测试场景设计
设计测试场景要考虑真实业务场景。比如电商系统要模拟:
- 浏览商品(80%请求)
- 加入购物车(15%)
- 下单支付(5%)
可以用JMeter的Throughput Controller来实现:
<ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="浏览商品" enabled="true"> <intProp name="ThroughputController.percent">80</intProp> </ThroughputController>测试时要循序渐进,先小规模测试,再逐步增加压力。我一般按照这个步骤:
- 单接口基准测试(100并发)
- 混合场景测试(200并发)
- 峰值压力测试(最大并发)
- 稳定性测试(中等压力长时间运行)
6. 性能问题快速诊断指南
遇到性能问题时,我有一套快速诊断流程:
- 先用
top看整体资源使用情况 - 用
vmstat 1看CPU、内存、I/O等待 - 用
iostat -x 1看磁盘负载 - 用
netstat -antp看网络连接 - 用
jstack或arthas看Java线程状态
对于MySQL,这几个命令特别有用:
-- 查看当前运行的所有SQL SHOW FULL PROCESSLIST; -- 查看锁等待 SELECT * FROM information_schema.INNODB_TRX; -- 查看慢查询 SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;有一次我们用这套方法,仅用10分钟就定位到一个死锁问题,而之前团队已经排查了2小时无果。