引言:你的代码正在杀死你的系统!
"凌晨3点,系统又崩了!"这是某独角兽企业CTO在朋友圈的绝望呐喊。当百万级QPS来袭,90%的Java开发者写的代码都会原形毕露。本文将揭露那些教科书不会告诉你的高并发实战黑科技,从电商秒杀到社交Feed流,手把手教你打造真正抗住双11的Java系统。准备好颠覆你的认知了吗?
一、线程池的死亡陷阱:从OOM到系统崩溃
1.1 业务场景:促销活动的订单风暴
// 致命错误示范:新手最爱的"万能"线程池
public class OrderService {
// 问题1:无界队列导致OOM
private static final ExecutorService executor =
Executors.newFixedThreadPool(200);
public void processOrder(Order order) {
executor.execute(() -> {
// 问题2:未捕获异常导致线程死亡
paymentService.pay(order);
inventoryService.deduct(order);
// 问题3:未设置超时导致死锁
logisticsService.create(order);
});
}
}
// 工业级线程池配置方案
@Bean("orderThreadPool")
public ThreadPoolTaskExecutor orderThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 = CPU核心数 * 2
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 最大线程数 = 核心线程数 * 3 (根据压测调整)
executor.setMaxPoolSize(executor.getCorePoolSize() * 3);
// 队列容量 = 最大线程数 * 10 (防止任务丢失)
executor.setQueueCapacity(executor.getMaxPoolSize() * 10);
// 线程存活时间(秒)
executor.setKeepAliveSeconds(60);
// 拒绝策略:交给调用线程处理(保证不丢失订单)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线程名前缀(便于排查问题)
executor.setThreadNamePrefix("order-process-");
// 等待所有任务完成再关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
// 等待终止超时时间(秒)
executor.setAwaitTerminationSeconds(60);
return executor;
}
// 使用示例
public CompletableFuture<Void> processOrderSafe(Order order) {
return CompletableFuture.runAsync(() -> {
try {
// 带超时的支付操作
paymentService.pay(order, 3, TimeUnit.SECONDS);
// 库存操作增加事务补偿
inventoryService.deductWithCompensation(order);
// 物流操作增加熔断
logisticsService.createWithCircuitBreaker(order);
} catch (Exception e) {
// 异常处理+重试机制
handleOrderFailure(order, e);
}
}, orderThreadPool);
}
关键技术点:
- 合理的线程池参数计算(CPU密集型 vs IO密集型)
- 完善的拒绝策略(CallerRunsPolicy > AbortPolicy)
- 线程池监控(Micrometer+Prometheus)
- 优雅停机方案(shutdownHook+awaitTermination)
二、分布式事务的终极解决方案:从TCC到Saga
2.1 业务场景:跨微服务的订单支付
// 分布式事务错误示范:本地事务+MQ
public void createOrder(Order order) {
// 本地事务
transactionTemplate.execute(status -> {
orderDao.save(order);
// 发送MQ消息(问题:消息可能发送失败)
mqTemplate.send("order.create", order.getId());
return true;
});
// 问题:本地事务成功但MQ消息丢失,导致数据不一致
}
2.2 高可用改造方案
// TCC模式实现(Try-Confirm-Cancel)
public class OrderTccService {
@Transactional
public void tryCreateOrder(Order order) {
// 1. 预留资源(冻结库存)
inventoryClient.freeze(order.getItems());
// 2. 创建临时订单(状态为处理中)
order.setStatus(OrderStatus.PROCESSING);
orderDao.save(order);
// 3. 记录事务日志
tccLogService.logTry(order.getId());
}
@Transactional
public void confirmCreateOrder(Long orderId) {
// 1. 确认订单状态
orderDao.updateStatus(orderId, OrderStatus.CONFIRMED);
// 2. 扣减真实库存
inventoryClient.deductFromFrozen(orderId);
// 3. 更新事务日志
tccLogService.logConfirm(orderId);
}
@Transactional
public void cancelCreateOrder(Long orderId) {
// 1. 取消订单
orderDao.updateStatus(orderId, OrderStatus.CANCELED);
// 2. 释放冻结库存
inventoryClient.releaseFrozen(orderId);
// 3. 更新事务日志
tccLogService.logCancel(orderId);
}
}
// 使用Seata Saga模式实现
@SagaStart
public void createOrderWithSaga(Order order) {
// 1. 创建订单(本地事务)
sagaService.step()
.name("创建订单")
.withCompensation(orderDao::delete, order.getId())
.invoke(() -> orderDao.save(order));
// 2. 扣减库存(远程服务)
sagaService.step()
.name("扣减库存")
.withCompensation(inventoryClient::restock, order.getItems())
.invoke(() -> inventoryClient.deduct(order.getItems()));
// 3. 创建物流单(远程服务)
sagaService.step()
.name("创建物流")
.withCompensation(logisticsClient::cancel, order.getId())
.invoke(() -> logisticsClient.create(order));
}
方案对比:
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 本地事务+MQ | 最终 | 高 | 低 | 简单业务 |
| TCC | 强 | 中 | 高 | 资金交易类业务 |
| Saga | 最终 | 较高 | 中 | 长流程业务 |
| Seata AT | 强 | 较低 | 低 | 简单分布式事务 |
三、缓存与数据库一致性:从双写到最终一致性
3.1 业务场景:商品价格更新
// 致命错误:先更新数据库再删缓存
public void updatePrice(Long itemId, BigDecimal price) {
// 1. 更新数据库
itemDao.updatePrice(itemId, price);
// 2. 删除缓存(可能失败)
redisTemplate.delete("item:" + itemId);
// 问题:缓存删除失败导致长期不一致
}
3.2 高可用改造方案
// 方案1:基于binlog的最终一致性(Canal+MQ)
public void updatePriceWithBinlog(Long itemId, BigDecimal price) {
// 1. 只更新数据库
itemDao.updatePrice(itemId, price);
// 2. Canal监听binlog发送MQ
// 3. 消费者处理缓存更新(带重试机制)
}
// 方案2:双写+本地消息表
public void updatePriceWithLocalMsg(Long itemId, BigDecimal price) {
// 1. 开启事务
transactionTemplate.execute(status -> {
// 2. 更新数据库
itemDao.updatePrice(itemId, price);
// 3. 写入本地消息表
messageDao.save(
new CacheMessage("item:" + itemId, "DELETE")
);
return true;
});
// 4. 定时任务扫描消息表处理缓存
}
// 方案3:延迟双删(应对极端情况)
public void updatePriceWithDelayDelete(Long itemId, BigDecimal price) {
// 1. 先删除缓存
redisTemplate.delete("item:" + itemId);
// 2. 更新数据库
itemDao.updatePrice(itemId, price);
// 3. 异步延迟再次删除
scheduledExecutor.schedule(() -> {
redisTemplate.delete("item:" + itemId);
}, 1, TimeUnit.SECONDS);
}
一致性保障方案对比:
-
强一致性方案:
- 分布式锁+事务(性能差)
- 2PC/3PC(复杂度高)
-
最终一致性方案:
- 本地消息表(推荐)
- MQ事务消息(RocketMQ)
- TCC模式
- 监听binlog(Canal)
四、总结:高并发架构的黄金法则
- 线程池四要素:合理参数 + 拒绝策略 + 监控 + 优雅停机
- 分布式事务选择:简单业务用MQ,复杂业务用Saga,资金交易用TCC
- 缓存一致性:binlog监听 > 本地消息表 > 延迟双删
- 降级策略:熔断 > 限流 > 降级 > 人工开关
- 监控告警:指标监控(Prometheus) + 日志追踪(ELK) + 全链路追踪(SkyWalking)
- 性能优化:压测发现瓶颈 > 优化 > 再压测(循环迭代)
- 代码规范:防御式编程 + 完备日志 + 统一异常处理
记住:没有完美的架构,只有适合业务的架构。当你的系统面临百万级并发时,希望你不会成为那个凌晨3点被叫醒的人!