Skip to content
清晨的一缕阳光
返回

性能调优实战

性能调优实战

性能调优是系统优化的核心环节,包括 JVM 调优、SQL 优化、系统调优等多个层面。本文详解性能调优的方法论、工具和实战技巧。

一、性能调优方法论

1.1 调优流程

graph TB
    A[1. 性能测试<br/>基准测试/负载测试/压力测试] --> B[2. 性能分析<br/>瓶颈定位/profiling/日志分析]
    B --> C[3. 优化实施<br/>代码优化/参数调优/架构优化]
    C --> D[4. 效果验证<br/>对比测试/监控观察/回归测试]
    D --> E[5. 持续优化<br/>定期性能测试/监控告警/容量规划]

1.2 性能指标

指标说明目标值
QPS每秒查询数根据业务定
TPS每秒事务数根据业务定
响应时间P50/P95/P99P99 < 500ms
错误率失败请求占比< 0.1%
CPU 使用率CPU 占用< 70%
内存使用率堆内存占用< 80%
GC 时间GC 暂停时间< 100ms

二、JVM 调优

2.1 JVM 内存结构

graph TB
    subgraph Heap[Heap 堆]
        direction LR
        subgraph Young[Young Gen]
            E[Eden]
            S1[Survivor 0]
            S2[Survivor 1]
        end
        O[Old Gen]
    end
    
    subgraph NonHeap[Non-Heap 非堆]
        M[Metaspace 元空间]
        C[Code Cache 代码缓存]
        S[Stack 栈]
    end
    
    Heap --> NonHeap

2.2 GC 算法选择

mindmap
  root((GC 算法选择))
    Serial
      吞吐量:低
      延迟:高
      适用:单线程
    Parallel
      吞吐量:高
      延迟:中
      适用:批处理
    CMS
      吞吐量:中
      延迟:低
      适用:Web 应用
    G1
      吞吐量:高
      延迟:低
      适用:大堆<32GB
    ZGC
      吞吐量:高
      延迟:极低
      适用:超大堆>32GB

2.3 JVM 参数配置

# 生产环境推荐配置(8GB 堆)

java -Xms8g -Xmx8g \                    # 堆大小(最小=最大)
     -XX:+UseG1GC \                     # 使用 G1 GC
     -XX:MaxGCPauseMillis=200 \         # 最大 GC 暂停时间
     -XX:G1HeapRegionSize=16m \         # G1 区域大小
     -XX:InitiatingHeapOccupancyPercent=45 \  # 并发 GC 触发阈值
     -XX:+ParallelRefProcEnabled \      # 并行引用处理
     -XX:+UseStringDeduplication \      # 字符串去重
     -XX:MaxMetaspaceSize=512m \        # 元空间大小
     -XX:ReservedCodeCacheSize=240m \   # 代码缓存大小
     -XX:+HeapDumpOnOutOfMemoryError \  # OOM 时 dump
     -XX:HeapDumpPath=/logs/ \          # dump 文件路径
     -Xlog:gc*:file=/logs/gc.log:time,uptime:filecount=5,filesize=100m \
     -jar app.jar

# 大堆配置(32GB+,使用 ZGC)
java -Xms32g -Xmx32g \
     -XX:+UseZGC \
     -XX:ZCollectionInterval=5 \
     -XX:ZAllocationSpikeTolerance=2 \
     -jar app.jar

2.4 GC 日志分析

# GC 日志示例
[GC (G1 Evacuation Pause) 2048M->512M(8192M), 0.0523456 secs]
[GC (G1 Concurrency Start) 2048M->2048M(8192M), 0.0001234 secs]
[GC (G1 Evacuation Pause) 3072M->768M(8192M), 0.0456789 secs]

# 分析工具
# 1. GCEasy(在线)
https://gceasy.io/

# 2. GCViewer(本地)
java -jar gcviewer.jar gc.log

# 3. 命令行分析
jstat -gcutil <pid> 1000 10

# 输出解读
S0     S1     E      O      M     CCS    YGC   YGCT    FGC   FGCT    GCT
0.00   0.00   50.00  40.00  80.00  10.00   100   5.000    2    0.500   5.500

S0/S1: Survivor 区使用率
E: Eden 区使用率
O: Old 区使用率
M: Metaspace 使用率
YGC/FGC: Young/Old GC 次数
YGCT/FGCT: Young/Old GC 总耗时

2.5 内存泄漏排查

# 1. 查看堆内存
jmap -heap <pid>

# 2. dump 堆内存
jmap -dump:format=b,file=heap.hprof <pid>

# 3. 分析堆 dump(MAT)
# 下载 Eclipse Memory Analyzer
# 打开 heap.hprof 文件

# 4. 查找泄漏嫌疑
# - Dominator Tree(支配树)
# - Histogram(直方图)
# - OOM Leaks(泄漏检测)

# 5. 常见泄漏原因
# - 静态集合类(static Map/List)
# - 未关闭的资源(Connection、Stream)
# - ThreadLocal 未清理
# - 监听器未注销

三、SQL 优化

3.1 执行计划分析

-- 查看执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'UNPAID';

-- 输出解读
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | orders | NULL       | index | idx_user      | idx_user| 8       | const| 100  |   10.00  | Using |
+----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------+

type 类型(性能从好到差):
├── system:系统表(最优)
├── const:主键/唯一索引
├── eq_ref:主键/唯一索引连接
├── ref:非唯一索引
├── range:索引范围扫描
├── index:全索引扫描
└── ALL:全表扫描(最差)

Extra 字段:
├── Using index:覆盖索引(好)
├── Using whereWHERE 过滤
├── Using temporary:临时表(需优化)
└── Using filesort:文件排序(需优化)

3.2 索引优化

-- 1. 创建复合索引(最左前缀原则)
CREATE INDEX idx_user_status ON orders(user_id, status, create_time);

-- 有效查询
SELECT * FROM orders WHERE user_id = 123;                    -- ✅
SELECT * FROM orders WHERE user_id = 123 AND status = 'A';   -- ✅
SELECT * FROM orders WHERE user_id = 123 AND status = 'A' AND create_time > '2024-01-01';  -- ✅

-- 无效查询
SELECT * FROM orders WHERE status = 'A';                     -- ❌
SELECT * FROM orders WHERE status = 'A' AND create_time > '2024-01-01';  -- ❌

-- 2. 覆盖索引(避免回表)
SELECT user_id, status FROM orders WHERE user_id = 123;  -- ✅ 覆盖索引

-- 3. 索引下推(5.6+)
SELECT * FROM orders WHERE user_id = 123 AND name LIKE '张%';
-- 索引下推:先在索引中过滤 name,再回表

-- 4. 避免索引失效
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';  -- ❌ 函数导致失效
SELECT * FROM orders WHERE create_time >= '2024-01-01 00:00:00' AND create_time < '2024-01-02 00:00:00';  -- ✅

SELECT * FROM orders WHERE user_id != 123;  -- ❌ 负向查询
SELECT * FROM orders WHERE user_id = 123 OR user_id = 456;  -- ✅ UNION ALL

SELECT * FROM orders WHERE name LIKE '%张%';  -- ❌ 前缀通配符
SELECT * FROM orders WHERE name LIKE '张%';  -- ✅

3.3 SQL 优化案例

-- 案例 1:分页优化
-- 优化前(深分页慢)
SELECT * FROM orders ORDER BY create_time DESC LIMIT 100000, 20;

-- 优化后(子查询)
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY create_time DESC LIMIT 100000, 20
) tmp ON o.id = tmp.id;

-- 优化后(延迟关联)
SELECT * FROM orders
WHERE create_time <= (SELECT create_time FROM orders ORDER BY create_time DESC LIMIT 100000, 1)
ORDER BY create_time DESC LIMIT 20;

-- 案例 2:JOIN 优化
-- 优化前(大表 JOIN)
SELECT o.*, u.name FROM orders o
LEFT JOIN user u ON o.user_id = u.id
WHERE o.create_time > '2024-01-01';

-- 优化后(小表驱动大表)
SELECT o.*, u.name FROM user u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.create_time > '2024-01-01';

-- 案例 3:UNION 优化
-- 优化前
SELECT * FROM orders WHERE user_id = 123
UNION
SELECT * FROM orders WHERE status = 'UNPAID';

-- 优化后
SELECT * FROM orders WHERE user_id = 123
UNION ALL  -- 不去重
SELECT * FROM orders WHERE status = 'UNPAID';

3.4 慢查询优化

-- 1. 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过 1 秒记录
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- 2. 查看慢查询
SHOW VARIABLES LIKE 'slow_query_log_file';

-- 3. 分析慢查询
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

-- 4. 使用 pt-query-digest
pt-query-digest /var/log/mysql/slow.log > slow_report.txt

-- 5. 慢查询报告
# 排名靠前的 SQL
# 执行次数、总时间、平均时间
# 建议添加索引

四、系统调优

4.1 Linux 内核参数

# /etc/sysctl.conf 配置

# 1. 文件句柄数
fs.file-max = 2097152
fs.nr_open = 2097152

# 2. TCP 参数
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.ip_local_port_range = 1024 65535

# 3. 内存参数
vm.swappiness = 10  # 减少 swap 使用
vm.overcommit_memory = 1  # 允许过度分配
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10

# 应用配置
sysctl -p

# 4. 用户限制(/etc/security/limits.conf)
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535

4.2 性能监控工具

# 1. CPU 监控
top                      # 实时进程监控
htop                     # 增强版 top
mpstat -P ALL 1          # 每核 CPU 使用率
pidstat -u 1             # 进程 CPU 使用率

# 2. 内存监控
free -h                  # 内存使用
vmstat 1                 # 虚拟内存统计
pidstat -r 1             # 进程内存使用

# 3. 磁盘 IO
iostat -x 1              # IO 统计
iotop                    # IO 进程监控
pidstat -d 1             # 进程 IO 使用

# 4. 网络监控
ss -tan                  # TCP 连接
netstat -tan             # 网络连接
iftop                    # 网络流量监控
tcpdump -i eth0 port 80  # 抓包分析

# 5. 综合监控
sar -u -r -d -n 1        # 系统活动报告
dstat                    # 全能监控工具

4.3 性能分析工具

# 1. Java Profiling
jstack <pid>             # 线程 dump
jmap <pid>               # 堆 dump
jstat <pid>              # GC 统计

# 2. Arthas(阿里开源)
java -jar arthas-boot.jar
# 常用命令:
thread -n 3              # 最忙线程
dashboard                # 仪表盘
trace                    # 方法调用追踪
profiler                 # CPU 分析

# 3. Async Profiler
./profiler.sh -d 30 -f profile.html <pid>
# 生成火焰图

# 4. Perf(Linux)
perf top                 # 实时性能分析
perf record -g           # 记录调用栈
perf report              # 分析报告

# 5. eBPF 工具
bpftrace -l 'tracepoint:*'  # 可用探针
bpftrace -e 'tracepoint:syscalls:sys_enter_open /pid == 123/ { printf("%s\n", comm); }'

五、性能测试

5.1 JMeter 压测

JMeter 压测配置

线程组配置:
├── 线程数:100-1000(根据场景)
├── Ramp-Up 时间:10-60 秒
└── 循环次数:永远/指定次数

HTTP 请求:
├── 服务器名称:order-service
├── 端口:8080
├── 方法:POST
└── 路径:/api/orders

监听器:
├── 查看结果树(调试用)
├── 聚合报告(核心指标)
├── 响应时间图
└── 后端监听器(InfluxDB + Grafana)

命令行执行:
jmeter -n -t test_plan.jmx -l result.jtl -e -o report_folder

5.2 压测报告

压测报告模板

测试场景:订单创建接口
测试时间:2024-01-01 10:00-11:00
并发用户:100
测试时长:30 分钟

测试结果:
├── 总请求数:1,800,000
├── QPS:1000
├── 平均响应时间:85ms
├── P95 响应时间:150ms
├── P99 响应时间:250ms
├── 错误率:0.05%
└── 吞吐量:50MB/s

资源使用:
├── CPU:65%
├── 内存:70%
├── 磁盘 IO:40%
└── 网络:30%

瓶颈分析:
├── 数据库慢查询:3 个 SQL 超过 500ms
├── GC 暂停:平均 50ms,最大 200ms
└── 线程池:活跃线程接近上限

优化建议:
├── 添加数据库索引
├── 调整 JVM 参数
└── 扩容线程池

六、总结

6.1 调优检查清单

性能调优检查清单

JVM 调优:
☐ 堆大小合理设置
☐ GC 算法选择合适
☐ GC 参数优化
☐ 无内存泄漏

SQL 优化:
☐ 慢查询分析
☐ 索引优化
☐ 执行计划分析
☐ 避免全表扫描

系统调优:
☐ 文件句柄数
☐ TCP 参数
☐ 内存参数
☐ 磁盘 IO

性能测试:
☐ 基准测试
☐ 负载测试
☐ 压力测试
☐ 稳定性测试

6.2 核心原则

  1. 数据驱动:基于监控数据,不盲目调优
  2. 逐步优化:一次只改一个参数
  3. 效果验证:优化后必须压测验证
  4. 持续监控:上线后持续观察
  5. 文档记录:记录所有调优过程和结果

性能调优是科学 + 艺术的结合。科学是方法论和工具,艺术是经验和直觉。持续学习、不断实践,才能成为性能调优高手。


分享这篇文章到:

上一篇文章
事件驱动架构设计
下一篇文章
可观测性体系建设