# 性能优化 ## 问题 1. 性能优化的常见思路和方法有哪些? 2. 如何进行系统性能瓶颈分析? 3. 数据库性能优化有哪些方法? 4. JVM 性能调优的常见问题和解决方案? 5. 接口性能优化有哪些技巧? 6. 如何进行全链路压测? 7. 在实际项目中如何进行性能优化? --- ## 标准答案 ### 1. 性能优化思路 #### **优化原则** 1. **测量优先**:先测量,后优化 2. **抓大放小**:优化瓶颈,而非所有地方 3. **数据驱动**:用数据说话 4. **权衡取舍**:优化往往带来复杂性 --- #### **优化层次** ``` 用户响应时间 ↓ 前端优化(减少请求、CDN、缓存) ↓ 网络优化(减少 RTT、压缩) ↓ 后端优化(代码、算法、并发) ↓ 数据库优化(索引、分库分表) ↓ 操作系统优化(TCP、内核参数) ↓ 硬件优化(CPU、内存、磁盘、网络) ``` --- ### 2. 性能瓶颈分析 #### **分析工具** **1. CPU 分析**: ```bash # top 命令 top -p # 查看 CPU 使用率 %CPU: 80.5 # 查看线程 CPU top -H -p ``` **2. 内存分析**: ```bash # 查看内存使用 free -h # 查看进程内存 ps aux | grep java # JVM 堆内存 jmap -heap ``` **3. 磁盘 I/O**: ```bash # iostat iostat -x 1 # 查看 I/O 等待 %iowait: 15.2 # 高说明磁盘瓶颈 ``` **4. 网络 I/O**: ```bash # 查看网络连接 netstat -anp | grep # 查看网络流量 iftop ``` --- #### **Java 性能分析工具** **1. JProfiler**: - CPU Profiler - Memory Profiler - Thread Profiler **2. Arthas(阿里开源)**: ```bash # 安装 curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar # 查看 CPU 最高的线程 dashboard # 查看方法调用耗时 trace com.example.UserService getUser # 查看类加载 sc -d com.example.User ``` **3. VisualVM**: - JDK 自带 - 可视化界面 --- ### 3. 数据库性能优化 #### **优化层次** ``` SQL 优化 ↓ 索引优化 ↓ 表结构优化 ↓ 数据库配置优化 ↓ 架构优化(读写分离、分库分表) ``` --- #### **SQL 优化** **1. 避免 SELECT \***: ```sql -- ❌ 查询所有列 SELECT * FROM users WHERE id = 1; -- ✅ 只查询需要的列 SELECT id, name, email FROM users WHERE id = 1; ``` **2. 避免 IN 子查询**: ```sql -- ❌ 子查询 SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE status = 1); -- ✅ JOIN SELECT o.* FROM orders o INNER JOIN users u ON o.user_id = u.id WHERE u.status = 1; ``` **3. 批量操作**: ```sql -- ❌ 单条插入 INSERT INTO orders (id, user_id) VALUES (1, 100); INSERT INTO orders (id, user_id) VALUES (2, 100); INSERT INTO orders (id, user_id) VALUES (3, 100); -- ✅ 批量插入 INSERT INTO orders (id, user_id) VALUES (1, 100), (2, 100), (3, 100); ``` **4. 使用 UNION ALL 代替 UNION**: ```sql -- ❌ UNION(去重,慢) SELECT name FROM users_a UNION SELECT name FROM users_b; -- ✅ UNION ALL(不去重,快) SELECT name FROM users_a UNION ALL SELECT name FROM users_b; ``` --- #### **索引优化** **1. 创建合适的索引**: ```sql -- 为查询条件创建索引 CREATE INDEX idx_user_id ON orders(user_id); -- 为排序创建索引 CREATE INDEX idx_created_at ON orders(created_at); -- 联合索引(注意最左前缀) CREATE INDEX idx_user_status ON orders(user_id, status); ``` **2. 避免索引失效**: ```sql -- ❌ 使用函数 SELECT * FROM orders WHERE YEAR(created_at) = 2024; -- ✅ 范围查询 SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'; -- ❌ 隐式转换 SELECT * FROM users WHERE phone = 13800138000; -- phone 是 VARCHAR -- ✅ 显式转换 SELECT * FROM users WHERE phone = '13800138000'; -- ❌ 前缀模糊查询 SELECT * FROM users WHERE name LIKE '%张'; -- ✅ 后缀模糊查询 SELECT * FROM users WHERE name LIKE '张%'; ``` --- #### **表结构优化** **1. 垂直拆分**: ```sql -- 拆分前(大表) CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100), password VARCHAR(100), intro TEXT, -- 简介(可能很长) settings JSON, -- 设置 created_at DATETIME ); -- 拆分后 CREATE TABLE users_base ( id BIGINT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100), password VARCHAR(100), created_at DATETIME ); CREATE TABLE users_ext ( user_id BIGINT PRIMARY KEY, intro TEXT, settings JSON ); ``` **2. 水平拆分**: ```sql -- 拆分前(单表数据量大) CREATE TABLE orders ( id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2), created_at DATETIME ); -- 1 亿条数据 -- 拆分后(按时间分表) CREATE TABLE orders_2024_01 ( id BIGINT PRIMARY KEY, user_id BIGINT, amount DECIMAL(10,2), created_at DATETIME ); CREATE TABLE orders_2024_02 ( ... ); ``` --- ### 4. JVM 性能调优 #### **常见问题** **1. 内存泄漏**: ```java // ❌ 内存泄漏 public class Cache { private static final Map cache = new HashMap<>(); public void put(String key, Object value) { cache.put(key, value); // 永不清理,内存泄漏 } } // ✅ 使用 Guava Cache public class Cache { private static final Cache cache = CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(10000) .build(); public void put(String key, Object value) { cache.put(key, value); } } ``` --- **2. 频繁 GC**: ```bash # 查看 GC 情况 jstat -gcutil 1000 # 输出 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 70.15 80.23 65.40 95.12 90.23 1523 45.236 10 12.345 57.581 # YGC:年轻代 GC 次数(正常 1000 次/小时以下) # FGC:老年代 GC 次数(应该接近 0) # GCT:总 GC 时间(应该 < 5%) ``` **调优**: ```bash # 调整堆内存大小 -Xms4g -Xmx4g # 调整年轻代大小 -Xmn2g # 使用 G1 收集器(大堆内存推荐) -XX:+UseG1GC # 调整 GC 并行线程 -XX:ParallelGCThreads=8 ``` --- **3. CPU 100%**: ```bash # 查看最耗 CPU 的线程 top -H -p # 转换线程 ID 为 16 进制 printf "%x" # 查看线程堆栈 jstack | grep -A 20 ``` --- ### 5. 接口性能优化 #### **优化技巧** **1. 异步处理**: ```java // ❌ 同步处理(慢) @PostMapping("/order") public String createOrder(@RequestBody Order order) { // 1. 保存订单(100ms) orderService.save(order); // 2. 发送邮件(500ms) emailService.send(order); // 3. 发送短信(300ms) smsService.send(order); // 总耗时:900ms return "success"; } // ✅ 异步处理(快) @PostMapping("/order") public String createOrder(@RequestBody Order order) { // 1. 保存订单(100ms) orderService.save(order); // 2. 异步发送邮件、短信 CompletableFuture.runAsync(() -> { emailService.send(order); smsService.send(order); }); // 总耗时:100ms return "success"; } ``` --- **2. 缓存**: ```java // ❌ 每次查询数据库 public User getUser(Long userId) { return userMapper.selectById(userId); // 50ms } // ✅ 使用缓存 @Cacheable(value = "users", key = "#userId") public User getUser(Long userId) { return userMapper.selectById(userId); // 第一次 50ms,后续 1ms } ``` --- **3. 批量查询**: ```java // ❌ 循环查询(N+1 问题) List orders = orderMapper.selectList(); for (Order order : orders) { User user = userMapper.selectById(order.getUserId()); // N 次查询 order.setUser(user); } // ✅ 批量查询 List orders = orderMapper.selectList(); Set userIds = orders.stream() .map(Order::getUserId) .collect(Collectors.toSet()); Map userMap = userMapper.selectByIds(userIds); // 1 次查询 orders.forEach(o -> o.setUser(userMap.get(o.getUserId()))); ``` --- **4. 连接池优化**: ```yaml # HikariCP 配置 spring: datasource: hikari: minimum-idle: 10 # 最小空闲连接 maximum-pool-size: 50 # 最大连接池大小 connection-timeout: 30000 # 连接超时时间 idle-timeout: 600000 # 空闲连接超时 max-lifetime: 1800000 # 连接最大生命周期 ``` --- ### 6. 全链路压测 #### **压测工具** **1. JMeter**: - 图形化界面 - 支持分布式压测 - 可录制脚本 **2. Locust(Python)**: ```python from locust import HttpUser, task, between class WebsiteUser(HttpUser): wait_time = between(1, 3) @task def index(self): self.client.get("/") @task(3) def api(self): self.client.get("/api/users") ``` **运行**: ```bash locust -f locustfile.py --host=https://example.com ``` --- #### **压测流程** ``` 1. 制定压测计划 - 确定目标(QPS、响应时间) - 确定场景(秒杀、日常流量) 2. 编写压测脚本 - 模拟真实用户行为 - 准备测试数据 3. 执行压测 - 逐步加压 - 记录指标 4. 分析结果 - 找出瓶颈 - 优化后再次压测 5. 容量评估 - 确定系统容量 - 预留缓冲(如 30%) ``` --- #### **压测指标** | 指标 | 说明 | 目标值 | |------|------|--------| | **QPS** | 每秒请求数 | 根据业务需求 | | **响应时间** | P99 < 200ms | 根据业务需求 | | **错误率** | 错误请求比例 | < 0.1% | | **CPU 使用率** | CPU 占用 | < 70% | | **内存使用率** | 内存占用 | < 80% | | **TPS** | 每秒事务数 | 根据业务需求 | --- ### 7. 实际项目案例 #### **案例 1:接口响应时间从 2s 优化到 100ms** **问题**: - 用户列表接口响应时间 2 秒 - 数据库查询慢 **分析**: ```sql EXPLAIN SELECT * FROM users WHERE status = 1; -- type: ALL(全表扫描) -- rows: 1000000 ``` **优化**: ```sql -- 1. 添加索引 CREATE INDEX idx_status ON users(status); -- 2. 只查询需要的字段 SELECT id, name, email FROM users WHERE status = 1; -- 3. 添加缓存 @Cacheable("users") ``` **结果**:响应时间降至 100ms --- #### **案例 2:解决频繁 GC 问题** **问题**: - 应用频繁 Full GC - CPU 使用率 100% **分析**: ```bash # 查看 GC 日志 jstat -gcutil 1000 # FGC 频繁,说明老年代内存不足 ``` **优化**: ```bash # 调整 JVM 参数 -Xms8g -Xmx8g # 增加堆内存 -Xmn4g # 调整年轻代大小 -XX:+UseG1GC # 使用 G1 收集器 -XX:MaxGCPauseMillis=200 # 设置最大 GC 暂停时间 ``` **结果**:Full GC 次数从每小时 10 次降至 0 次 --- ### 8. 阿里 P7 加分项 **深度理解**: - 理解性能优化的底层原理(操作系统、网络、数据库) - 理解各种性能分析工具的实现原理 - 理解性能测试的统计学方法 **实战经验**: - 有将接口从秒级优化到毫秒级的经验 - 有解决线上性能问题的经验 - 有全链路压测的经验 **架构能力**: - 能设计高性能架构 - 能设计性能监控体系 - 能制定性能优化规范 **技术选型**: - 了解各种性能分析工具(JProfiler、Arthas、SkyWalking) - 了解分布式追踪系统(Zipkin、Jaeger) - 能根据业务特点制定性能指标