主题
性能优化与监控
面试官喜欢问"你的项目有没有做过性能优化"。本章给商城加上性能意识,把"我会写代码"升级成"我会交付好的系统"。
一、性能优化心法
优化前:先量化(压测/日志),找瓶颈,再动手
优先级:能用 → 稳定 → 快(别过早优化)
优化点:前端 → 后端 → 数据库 → 缓存/架构别拍脑袋优化:先测出瓶颈在哪一层,再对症下药。
二、前端优化
| 手段 | 做法 | 收益 |
|---|---|---|
| 代码分割 | 路由级懒加载:() => import(...) | 首屏体积减小 |
| 组件库按需引入 | Element Plus / AntD 按需打包 | 体积大减 |
| 图片懒加载 | 图片 loading="lazy" | 滚动才加载 |
| 防抖/节流 | 搜索框 debounce、滚动节流 | 少发请求 |
| 骨架屏 | 加载中先渲染占位 | 体感更快 |
| CDN | 静态资源走 CDN | 全国访问快 |
前端路由懒加载(第 4/5 部分已经这么写了):
ts
// Vue
{ path: '/order/list', component: () => import('@/views/OrderListView.vue') }
// React
const OrderList = lazy(() => import('@/views/OrderList'))Vite 构建体积分析:
bash
cd web-vue && npx vite build --minify esbuild
npx vite-bundle-visualizer # 可视化看哪个包大三、后端优化
| 手段 | 做法 |
|---|---|
| 分页收敛 | 禁止无分页全量查询,列表必须分页 |
| 减少 N+1 查询 | 循环查库 → 批量 selectBatchIds / JOIN |
| 索引对齐 | 高频查询字段建索引(第 7 部分) |
| 接口幂等 | 重复提交下单加防重(token/唯一订单号) |
| 异步化 | 耗时操作(发消息/通知)用线程池异步 |
| 池化 | 连接池参数(HikariCP 默认已够) |
N+1 问题示例(商城购物车组装常见):
java
// ❌ 循环里查库:10 个条目 = 1 + 10 次查询
for (CartItem item : items) {
Product p = productMapper.selectById(item.getProductId()); // 每次查库
}
// ✅ 批量查一次
List<Long> ids = items.stream().map(CartItem::getProductId).toList();
List<Product> products = productMapper.selectBatchIds(ids); // 1 次
Map<Long, Product> map = products.stream().collect(toMap(Product::getId, p -> p));四、数据库优化(最立竿见影)
- 慢 SQL 日志:
slow_query_log打开,抓慢查询。 - EXPLAIN 分析:
type=ALL就加索引(第 7 部分讲过)。 - 避免 SELECT *:只取需要的列。
- 大表拆小(进阶):按时间/用户拆分。
sql
-- 开启慢查询日志(MySQL 8)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过 1 秒记录
SHOW VARIABLES LIKE 'slow_query%';五、缓存:Redis(进阶亮点)
热点商品(商品详情)加 Redis 缓存,大幅降低数据库压力:
查询:Redis 有 → 直接返回
Redis 无 → 查 MySQL → 写回 Redis(设置过期时间)
更新:写 MySQL → 删 Redis(下次查询重建)java
@Service
public class ProductService {
private final StringRedisTemplate redis;
public ProductVO detail(Long id) {
// 1. 查缓存
String cached = redis.opsForValue().get("product:" + id);
if (cached != null) {
return JSON.parseObject(cached, ProductVO.class);
}
// 2. 缓存未命中,查库 + 回填
Product product = productMapper.selectById(id);
redis.opsForValue().set("product:" + id,
JSON.toJSONString(toVO(product)), 30, TimeUnit.MINUTES);
return toVO(product);
}
}缓存三个经典坑(面试常问)
- 缓存穿透:查不存在的 id 每次都打库 → 布隆过滤器 / 缓存空值。
- 缓存击穿:热点 key 过期瞬间高并发打库 → 互斥锁 / 逻辑过期。
- 缓存雪崩:大量 key 同时过期 → 过期时间加随机值。
六、监控与告警
应用层
yaml
# application.yml 开启 Actuator
spring-boot-starter-actuator
management:
endpoints:
web:
exposure: include: health,metrics,infoGET /actuator/health → {"status":"UP"}
GET /actuator/metrics → 各项指标基础设施
| 方案 | 用途 |
|---|---|
| Prometheus + Grafana | 服务器 CPU/内存/磁盘 + 可视化大盘 |
| 阿里云/腾讯云监控 | 云服务器一键接入,有告警 |
| 日志中心(ELK) | 集中日志检索(进阶) |
告警规则示例
- CPU 使用率 > 85% 持续 5 分钟 → 告警
- 磁盘 > 80% → 告警
- 接口 5xx 比例 > 1% → 告警
- 慢接口 P99 > 2s → 告警
七、给商城项目加一个"优化点"(面试素材)
把下面的任何一个做了,面试都有话讲:
- [ ] 商品详情页加 Redis 缓存
- [ ] 商品列表接口加慢查询监控 + 索引
- [ ] 前端路由懒加载 + 构建体积分析
- [ ] 写一个 Jmeter 压测脚本,给出接口 QPS 数据
- [ ] 配置 /actuator/health 探活
面试说"我做了一个压测,下单接口并发 50 时 QPS 从 120 提升到 380(优化后)"——比背八股有说服力得多。