Skip to content

性能优化指南

作者:唐亚峰 | battcn
字数统计:1.5k 字

学习目标

掌握 Wemirr Platform 性能优化的方法和技巧

性能优化原则

┌─────────────────────────────────────────────────────────┐
│                    性能优化原则                          │
├─────────────────────────────────────────────────────────┤
│  1. 先测量,再优化(不要盲目优化)                        │
│  2. 优化瓶颈点(二八法则)                               │
│  3. 空间换时间(缓存)                                   │
│  4. 异步化、并行化                                       │
│  5. 减少网络往返                                        │
└─────────────────────────────────────────────────────────┘

数据库优化

索引优化

sql
-- 1. 为常用查询条件添加索引
CREATE INDEX idx_tenant_status ON t_order(tenant_id, status);
CREATE INDEX idx_create_time ON t_order(create_time);

-- 2. 覆盖索引(避免回表)
CREATE INDEX idx_cover ON t_order(tenant_id, status, order_no, amount);

-- 3. 前缀索引(大字段)
CREATE INDEX idx_name ON t_customer(name(10));

-- 4. 避免索引失效的情况
-- ❌ 函数操作
SELECT * FROM t_order WHERE DATE(create_time) = '2024-01-01';
-- ✅ 范围查询
SELECT * FROM t_order WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';

-- ❌ 隐式类型转换
SELECT * FROM t_order WHERE order_no = 123;  -- order_no 是 varchar
-- ✅ 类型匹配
SELECT * FROM t_order WHERE order_no = '123';

-- ❌ 前缀模糊查询
SELECT * FROM t_order WHERE order_no LIKE '%123';
-- ✅ 后缀模糊查询(可用索引)
SELECT * FROM t_order WHERE order_no LIKE '123%';

慢查询优化

sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

-- 分析执行计划
EXPLAIN SELECT * FROM t_order WHERE tenant_id = 1 AND status = 1;

-- 优化建议
-- 1. 避免 SELECT *
SELECT id, order_no, amount FROM t_order WHERE ...;

-- 2. 分页优化(深度分页)
-- ❌ 大偏移量
SELECT * FROM t_order LIMIT 100000, 10;
-- ✅ 游标分页
SELECT * FROM t_order WHERE id > 100000 ORDER BY id LIMIT 10;

-- 3. 批量操作
-- ❌ 循环插入
for (Order order : orders) {
    orderMapper.insert(order);
}
-- ✅ 批量插入
orderMapper.insertBatch(orders);

分库分表

当单表数据量超过 1000W 时,考虑分库分表:

java
// 使用 ShardingSphere
// 按租户分库
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds_$->{0..2}.t_order

// 按时间分表
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=create_time
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=t_order_inline

缓存优化

多级缓存架构

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   本地缓存   │ ──▶ │  Redis 缓存  │ ──▶ │   数据库    │
│  (Caffeine) │     │             │     │   (MySQL)   │
└─────────────┘     └─────────────┘     └─────────────┘
     10μs              1-5ms              10-100ms

本地缓存

java
@Configuration
public class CacheConfig {
    
    @Bean
    public Cache<String, Object> localCache() {
        return Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .recordStats()
            .build();
    }
}

// 使用
@Service
@RequiredArgsConstructor
public class DictService {
    
    private final Cache<String, Object> localCache;
    private final StringRedisTemplate redisTemplate;
    
    public List<DictItem> getByCode(String code) {
        String key = "dict:" + code;
        
        // 1. 查本地缓存
        Object cached = localCache.getIfPresent(key);
        if (cached != null) {
            return (List<DictItem>) cached;
        }
        
        // 2. 查 Redis 缓存
        String json = redisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            List<DictItem> items = JsonUtil.toList(json, DictItem.class);
            localCache.put(key, items);
            return items;
        }
        
        // 3. 查数据库
        List<DictItem> items = dictMapper.selectByCode(code);
        redisTemplate.opsForValue().set(key, JsonUtil.toJson(items), 1, TimeUnit.HOURS);
        localCache.put(key, items);
        return items;
    }
}

缓存一致性

java
// 更新数据时删除缓存
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = "dict", key = "#dto.code")
public void update(DictDTO dto) {
    dictMapper.updateById(dto);
    
    // 通知其他节点清除本地缓存
    redisTemplate.convertAndSend("cache:evict", "dict:" + dto.code);
}

// 监听缓存清除消息
@Component
@RequiredArgsConstructor
public class CacheEvictListener {
    
    private final Cache<String, Object> localCache;
    
    @PostConstruct
    public void subscribe() {
        redisTemplate.getConnectionFactory().getConnection()
            .subscribe((message, pattern) -> {
                String key = new String(message.getBody());
                localCache.invalidate(key);
            }, "cache:evict".getBytes());
    }
}

缓存穿透防护

java
// 1. 布隆过滤器
@Service
public class UserService {
    
    private final BloomFilter<Long> userIdFilter;
    
    public User getById(Long id) {
        // 先检查布隆过滤器
        if (!userIdFilter.mightContain(id)) {
            return null;
        }
        // 继续查询
        return userMapper.selectById(id);
    }
}

// 2. 缓存空值
public User getById(Long id) {
    String key = "user:" + id;
    String cached = redisTemplate.opsForValue().get(key);
    
    if ("NULL".equals(cached)) {
        return null;  // 缓存的空值
    }
    
    if (cached != null) {
        return JsonUtil.toObject(cached, User.class);
    }
    
    User user = userMapper.selectById(id);
    if (user == null) {
        // 缓存空值,较短过期时间
        redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
    } else {
        redisTemplate.opsForValue().set(key, JsonUtil.toJson(user), 1, TimeUnit.HOURS);
    }
    return user;
}

接口优化

批量处理

java
// ❌ 循环调用
for (Long userId : userIds) {
    User user = userService.getById(userId);
    // ...
}

// ✅ 批量查询
List<User> users = userService.listByIds(userIds);
Map<Long, User> userMap = users.stream()
    .collect(Collectors.toMap(User::getId, Function.identity()));

并行处理

java
@Service
public class OrderDetailService {
    
    public OrderDetailVO getOrderDetail(Long orderId) {
        OrderDetailVO vo = new OrderDetailVO();
        
        // 并行获取数据
        CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(
            () -> orderMapper.selectById(orderId)
        );
        CompletableFuture<List<OrderItem>> itemsFuture = CompletableFuture.supplyAsync(
            () -> orderItemMapper.selectByOrderId(orderId)
        );
        CompletableFuture<User> userFuture = orderFuture.thenCompose(
            order -> CompletableFuture.supplyAsync(
                () -> userMapper.selectById(order.getUserId())
            )
        );
        
        // 等待所有完成
        CompletableFuture.allOf(orderFuture, itemsFuture, userFuture).join();
        
        // 组装结果
        vo.setOrder(orderFuture.join());
        vo.setItems(itemsFuture.join());
        vo.setUser(userFuture.join());
        
        return vo;
    }
}

异步处理

java
@Service
public class OrderService {
    
    @Async
    public void sendOrderNotification(Order order) {
        // 发送邮件
        emailService.send(order.getEmail(), "订单创建成功", "...");
        // 发送短信
        smsService.send(order.getMobile(), "...");
        // 推送消息
        pushService.push(order.getUserId(), "...");
    }
    
    @Transactional
    public Order createOrder(OrderDTO dto) {
        Order order = doCreateOrder(dto);
        
        // 异步发送通知,不阻塞主流程
        sendOrderNotification(order);
        
        return order;
    }
}

接口响应压缩

yaml
# application.yml
server:
  compression:
    enabled: true
    mime-types: application/json,application/xml,text/html,text/plain
    min-response-size: 1024

微服务配置优化

优雅停机与虚拟线程

yaml
server:
  shutdown: graceful                    # 开启优雅停机

spring:
  threads:
    virtual:
      enabled: true                     # 开启全局虚拟线程(JDK 21+)
  reactor:
    context-propagation: auto           # 上下文自动传播
  lifecycle:
    timeout-per-shutdown-phase: 30s     # 给足时间处理旧请求

Feign 与负载均衡配置

yaml
spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true                 # 使用 Apache HttpClient 5
      client:
        config:
          default:                      # 全局配置
            connect-timeout: 1000       # 连接超时 1s(K8s 内部超 1s 通常 IP 已死)
            read-timeout: 60000         # 读取超时 60s
    loadbalancer:
      nacos:
        enabled: true                   # 使用 Nacos 负载均衡
      retry:
        enabled: true                   # 开启重试
        max-retries-on-same-service-instance: 0   # 不在同一个坏 IP 上死磕
        max-retries-on-next-service-instance: 2   # 切换到下一个实例重试 2 次
        retryable-status-codes: 503, 500          # 针对哪些状态码重试

监控与链路追踪

yaml
management:
  tracing:
    enabled: true
    sampling:
      probability: 1.0                  # 采样率 100%(生产可调低)
    propagation:
      type: w3c, b3                     # 支持 W3C 和 B3 格式
    baggage:
      remote-fields: x-request-id
      correlation:
        fields: x-request-id
  endpoints:
    web:
      exposure:
        include: "health,info,prometheus"   # 只暴露必要端点

JVM 优化

内存配置

bash
# 生产环境推荐配置
JAVA_OPTS="-Xms2g -Xmx2g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/app/logs/heapdump.hprof \
  -Xlog:gc*:file=/app/logs/gc.log:time,uptime:filecount=5,filesize=10M"

GC 调优

bash
# G1 收集器(推荐)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m

# 大堆内存场景
-XX:+UseZGC  # JDK 17+

监控与诊断

接口性能监控

java
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
    
    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long start = System.currentTimeMillis();
        String method = point.getSignature().toShortString();
        
        try {
            return point.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            if (cost > 1000) {
                log.warn("慢接口: {} 耗时: {}ms", method, cost);
            } else {
                log.debug("接口: {} 耗时: {}ms", method, cost);
            }
        }
    }
}

性能指标采集

yaml
# Prometheus 指标
management:
  endpoints:
    web:
      exposure:
        include: prometheus,health,info
  metrics:
    tags:
      application: ${spring.application.name}

性能检查清单

检查项标准
接口响应时间P99 < 500ms
数据库查询单次 < 100ms
缓存命中率> 90%
CPU 使用率< 70%
内存使用率< 80%
GC 停顿时间< 200ms

下一步