Skip to content

性能优化与监控

面试官喜欢问"你的项目有没有做过性能优化"。本章给商城加上性能意识,把"我会写代码"升级成"我会交付好的系统"。

一、性能优化心法

优化前:先量化(压测/日志),找瓶颈,再动手
优先级:能用 → 稳定 → 快(别过早优化)
优化点:前端 → 后端 → 数据库 → 缓存/架构

别拍脑袋优化:先测出瓶颈在哪一层,再对症下药。

二、前端优化

手段做法收益
代码分割路由级懒加载:() => 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));

四、数据库优化(最立竿见影)

  1. 慢 SQL 日志slow_query_log 打开,抓慢查询。
  2. EXPLAIN 分析type=ALL 就加索引(第 7 部分讲过)。
  3. 避免 SELECT *:只取需要的列。
  4. 大表拆小(进阶):按时间/用户拆分。
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,info
GET /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(优化后)"——比背八股有说服力得多。

基于 MIT 协议发布,可自由学习与修改