Skip to content
第 241 / 250 章架构⏱ 12 分钟阅读

第 241 章:性能调优 - 缓存与并发

学习目标

  • 高级缓存策略
  • 异步与并发优化
  • 响应式与高并发
  • 综合调优方法论

一、缓存进阶

1.1 多级缓存穿透流程

1.2 缓存策略对比

策略适用复杂度
Read-Aside通用
Write-Through读多写少
Write-Behind写多,允许丢失
Refresh-Ahead高频热点

1.3 缓存预热

java
@Component
public class CacheWarmer {

    @Autowired
    private Cache<String, Product> localCache;
    @Autowired
    private RedisTemplate<String, Product> redis;

    public void warm(StopWatch sw) {
        sw.start("加载热门商品");
        List<Long> topIds = productRepo.findTopIds(0, 5000);

        Map<String, Product> map = new HashMap<>();
        for (Long id : topIds) {
            Product p = productRepo.findById(id);
            if (p != null) {
                map.put("product:" + id, p);
                localCache.put(id.toString(), p);
            }
        }

        redis.opsForValue().multiSet(map);
        sw.stop();
        log.info("预热完成,加载 {} 条", topIds.size());
    }
}

1.4 缓存击穿-分布式锁

java
public Product getHotProduct(Long id) {
    Product p = cache.get("p:" + id);
    if (p != null) return p;

    // 分布式锁
    RLock lock = redisson.getLock("lock:p:" + id);
    if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
        try {
            // 二次检查
            p = cache.get("p:" + id);
            if (p == null) {
                p = dbMapper.findById(id);
                if (p != null) {
                    cache.put("p:" + id, p, 60, TimeUnit.SECONDS);
                }
            }
        } finally {
            lock.unlock();
        }
    } else {
        // 等待 50ms 重试
        return retry();
    }
    return p;
}

1.5 业务分离缓存

yaml
缓存按业务分类:
  - 用户缓存: user:{id}
  - 商品缓存: product:{id}
  - 配置缓存: config:{key}
  - 列表缓存: list:{key}:page:{p}
  - 计数缓存: count:{key}

二、Redis 性能

2.1 慢命令

bash
# 慢日志
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET

# 配置
slowlog-log-slower-than 10000  # 10ms
slowlog-max-len 128

常见慢命令:

  • KEYS *(生产禁用)
  • HGETALL(大 hash)
  • ZRANGE(大 zset)
  • SUNION(大 set)
  • SMEMBERS(大 set)

替代:

  • SCAN(替代 KEYS)
  • 拆分大 key
  • 用 ID 列表缓存

2.2 Pipeline

java
public List<Product> batchQuery(List<Long> ids) {
    return redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
        for (Long id : ids) {
            connection.stringCommands().get(("p:" + id).getBytes());
        }
        return null;
    });
}

减少 RTT,适合批量操作。

2.3 Lua 脚本

lua
-- 多 key 原子操作
local p1 = redis.call('GET', KEYS[1])
local p2 = redis.call('GET', KEYS[2])
if p1 and p2 then
    return {p1, p2}
end
return nil

2.4 客户端优化

yaml
Lettuce 默认: Netty,共享连接
Jedis: 阻塞,需连接池

lettuce:
  pool:
    enabled: true
    max-active: 16
    max-idle: 8

2.5 大 key 处理

bash
# 找大 key
redis-cli --bigkeys

# OBJECT IDLETIME
OBJECT IDLETIME bigkey

拆分方案:

  • 大 JSON → HASH 字段
  • 大 set → 多 set + 集合运算
  • 大 zset → 滑动窗口

2.6 Redis 内存淘汰

yaml
maxmemory 4gb
maxmemory-policy allkeys-lfu   # 优先淘汰最不频繁
# 备选: allkeys-lru / volatile-lru

2.7 Redis Cluster

三、并发优化

3.1 ForkJoin 并行处理

java
public class ParallelProcessor extends RecursiveTask<Long> {

    private final List<Long> numbers;

    public ParallelProcessor(List<Long> numbers) {
        this.numbers = numbers;
    }

    @Override
    protected Long compute() {
        if (numbers.size() < 1000) {
            return numbers.stream().mapToLong(Long::longValue).sum();
        }

        // 拆分
        int mid = numbers.size() / 2;
        ParallelProcessor left = new ParallelProcessor(numbers.subList(0, mid));
        ParallelProcessor right = new ParallelProcessor(numbers.subList(mid, numbers.size()));

        left.fork();
        return right.compute() + left.join();
    }
}

// 使用 ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
Long result = pool.invoke(new ParallelProcessor(bigList));

3.2 CompletableFuture

java
public CompletableFuture<UserDTO> getUserAsync(Long id) {
    return CompletableFuture.supplyAsync(() -> userRepo.findById(id))
        .thenApply(this::toDTO)
        .thenCompose(dto -> enrichWithOrders(dto));
}

public UserDTO enrichWithOrders(UserDTO dto) {
    return CompletableFuture.supplyAsync(() -> {
        dto.setOrders(orderService.findByUser(dto.getId()));
        return dto;
    }).join();
}

并行多个独立操作:

java
public Map<String, Object> dashboard(Long userId) {
    CompletableFuture<UserDTO> user = CompletableFuture
        .supplyAsync(() -> userService.findById(userId));

    CompletableFuture<List<Order>> orders = CompletableFuture
        .supplyAsync(() -> orderService.findByUser(userId));

    CompletableFuture<List<Coupon>> coupons = CompletableFuture
        .supplyAsync(() -> couponService.findByUser(userId));

    CompletableFuture.allOf(user, orders, coupons).join();

    return Map.of(
        "user", user.join(),
        "orders", orders.join(),
        "coupons", coupons.join()
    );
}

3.3 异步 I/O

Servlet 3.0+ 异步

java
@GetMapping("/async")
public CompletableFuture<String> async() {
    return CompletableFuture.supplyAsync(() -> {
        try { Thread.sleep(100); } catch (Exception e) {}
        return "done";
    });
}

WebFlux 响应式

java
@GetMapping("/users/{id}")
public Mono<UserDTO> getUser(@PathVariable Long id) {
    return userService.findById(id)
        .map(this::toDTO);
}

@GetMapping("/users")
public Flux<UserDTO> list() {
    return userService.findAll()
        .map(this::toDTO);
}

优势:

  • 少量线程处理大量并发
  • 适合 IO 密集型

劣势:

  • 学习曲线陡
  • 异步传染

四、消息队列优化

4.1 批量消费

java
@KafkaListener(topics = "orders", batch = "true")
public void batchConsume(List<OrderMessage> messages) {
    for (OrderMessage msg : messages) {
        orderService.process(msg);
    }
    // 一次 ack,少网络
}

4.2 异步 Commit

yaml
spring:
  kafka:
    consumer:
      ack-mode: manual_immediate
java
@KafkaListener(...)
public void onMessage(OrderMessage msg,
                     @Header(KafkaHeaders.OFFSET) List<Long> offsets,
                     Acknowledgment ack) {
    try {
        orderService.process(msg);
        ack.acknowledge();
    } catch (Exception e) {
        // 不 ack,下次重试
    }
}

4.3 消费限速

yaml
spring:
  kafka:
    listener:
      concurrency: 3
      max-poll-records: 500
java
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
    factory.setBatchListener(true);
    factory.setConcurrency(3);
    factory.getContainerProperties().setMaxPollRecords(500);
    return factory;
}

五、Tomcat 调优

yaml
server:
  tomcat:
    threads:
      max: 200                # 最大工作线程
      min-spare: 50
    accept-count: 100         # accept queue
    max-connections: 10000    # 最大连接
    connection-timeout: 20s
    keep-alive-timeout: 60s
    mbeanregistry:
      enabled: true

# APR Tomcat(高并发)
# 切换 native 模式

公式:

线程数 = CPU 核数 * 2 + 1 (计算型)
        = (IO 平均耗时 / CPU 耗时 + 1) * CPU 核数 (IO 型)

六、NIO 与 Zero Copy

java
// ❌ 4 次拷贝(2 次 DMA + 2 次 CPU)
FileInputStream.read()
JVM buffer
Socket.send()

// ✅ Zero Copy
FileChannel channel = new FileInputStream(file).getChannel();
channel.transferTo(0, channel.size(), socketChannel);
// 2 次 DMA 拷贝

工具:

  • Netty
  • OkHttp
  • Kafka 内部

七、压缩与序列化

7.1 JSON 序列化

java
// Jackson 调优
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.registerModule(new JavaTimeModule());
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

// 字段不序列化
@JsonIgnore
private String internalField;

7.2 字节码大小

yaml
选择:
  - Jackson: 通用
  - Gson: 简单
  - Protobuf: 高性能 + IDL
  - Kryo: 高性能二进制
  - Hibernate Validator: 校验

7.3 压缩

yaml
spring:
  gzip:
    enabled: true
    min-request-size: 1024
    mime-types: application/json,text/html

server:
  compression:
    enabled: true
    mime-types: application/json,application/xml
    min-response-size: 1024

八、网络优化

8.1 Keep-Alive

yaml
server:
  tomcat:
    keep-alive-timeout: 60s
    max-keep-alive-requests: 100

8.2 Netty 调优

java
ServerBootstrap b = new ServerBootstrap();
b.childOption(ChannelOption.SO_RCVBUF, 32 * 1024)
 .childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
 .childOption(ChannelOption.TCP_NODELAY, true)         // 关闭 Nagle
 .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
 .childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 64 * 1024);

8.3 长连接 vs 短连接

yaml
内网: 长连接(keep-alive)
外部: HTTP/2 多路复用
RPC: gRPC(HTTP/2 + Protobuf)

九、响应式编程(Spring WebFlux)

9.1 适用场景

yaml
适合:
  - 大量 IO
  - 长连接
  - 流式响应
  - 实时推送

不适合:
  - CPU 密集
  - 大量阻塞库(JDBC)
  - 小并发

9.2 实际应用

java
@RestController
public class ReactiveController {

    @GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> sse() {
        return Flux.interval(Duration.ofSeconds(1))
            .map(i -> "data: " + Instant.now())
            .take(60);
    }

    @GetMapping("/products")
    public Flux<ProductDTO> allProducts() {
        return reactiveRepo.findAll();
    }
}

十、配置层面优化

10.1 连接复用

java
// HikariCP 默认池化连接
// Netty Channel 复用
// Redis Lettuce 连接复用
// HTTP/2 多路复用

10.2 超时设置

yaml
feign:
  client:
    config:
      default:
        connectTimeout: 3000    # 3 秒
        readTimeout: 8000

# 总是设置超时,默认 60 秒太长

10.3 限流保护

yaml
resilience4j:
  bulkhead:
    instances:
      inventory:
        maxConcurrentCalls: 100
        maxWaitDuration: 500ms

feign:
  sentinel:
    enabled: true

十一、典型调优案例

11.1 P99 突然飙到 5 秒

排查步骤:

yaml
1. 检查最近的发布: git diff HEAD~5
2. 看 GC 日志:最近是不是 GC 频繁
3. 看慢查询: db 是不是挂了
4. 看 Network IO:网卡是不是跑满
5. 看磁盘:磁盘 IO 是瓶颈?
6. 用 Arthas:trace 慢方法栈

11.2 QPS 没涨但 P99 涨

yaml
可能原因:
  - 数据量变大(分页越查越慢)
  - 内存泄漏(stw 时间增长)
  - 索引失效(新加数据导致)
  - 缓存命中率下降

11.3 CPU 飙到 100%

bash
# 1. 找 hot thread
top -Hp <pid>

# 2. jstack 拿到线程栈
printf '%x\n' <tid>
jstack <pid> | grep <hex_tid>

# 3. Arthas dashboard
thread -n 3

十二、性能方法论

12.1 二八原则

yaml
80% 性能问题集中在 20% 代码
→ 火焰图找热点
→ 优化这 20%

12.2 调优顺序

yaml
1. 排查瓶颈(基准测)
2. SQL 和索引
3. 缓存策略
4. 并发模型
5. GC/JVM
6. 架构改造

12.3 A/B 测试

yaml
所有优化都要:
  - 可量化(具体指标改善多少)
  - 可对比(A/B 测试)
  - 可回退(灰度发布)

十三、本章小结

主题关键
缓存多级 + 预热 + 防三大问题
Redis命令 + Pipeline + Cluster
并发线程池 + 异步 + 无锁
响应式WebFlux + Netty
配置超时 + 限流 + Keep-Alive

动手练习

  1. 用 CompletableFuture 并行查询 3 个独立服务
  2. 配置 Redis Cluster,主从切换 < 1 秒
  3. 用火焰图找出 CPU 热点并优化
  4. 设置完整的超时与限流,观察压测变化

下一章:第 242 章:综合调优实战

本站基于 VitePress 构建 · 由 Codebook 团队维护