如何设计高并发系统:从架构原理到实战落地
高并发系统设计不是简单的"加机器",而是要在流量、数据、服务三个维度上做系统性优化。本文从真实场景出发,拆解高并发系统的设计原理与落地实践。
一、高并发系统的核心挑战
1.1 问题本质
当并发请求量超过系统处理能力时,会出现以下问题:
| 挑战类型 | 表现症状 | 根本原因 | 典型场景 |
|---|---|---|---|
| 流量洪峰 | 响应时间飙升、服务雪崩 | 请求量超过系统承载能力 | 电商大促、秒杀活动 |
| 热点数据 | 缓存击穿、数据库压力集中 | 少量Key承载大部分流量 | 热门商品、明星用户 |
| 资源竞争 | 死锁、连接池耗尽 | 共享资源访问冲突 | 库存扣减、订单创建 |
| 数据一致性 | 超卖、数据不一致 | 分布式环境下的并发写入 | 秒杀、抢购 |
1.2 性能指标参考
| 业务场景 | QPS峰值 | 响应时间要求 | 可用性要求 |
|---|---|---|---|
| 普通电商 | 1,000-10,000 | <500ms | 99.9% |
| 大促活动 | 100,000-1,000,000 | <200ms | 99.99% |
| 秒杀系统 | 1,000,000+ | <100ms | 99.999% |
| 实时推荐 | 10,000-50,000 | <300ms | 99.95% |
1.3 设计目标
在保证可用性 和数据一致性的前提下,最大化系统吞吐量。
CAP权衡:
- CA(一致性 + 可用性):适合强一致场景,如金融交易
- CP(一致性 + 分区容错):适合数据一致性要求高场景,如库存管理
- AP(可用性 + 分区容错):适合高可用场景,如商品浏览
实战建议 :大多数互联网业务选择最终一致性(BASE理论),通过补偿机制保证数据最终一致。
二、流量治理:从源头控制请求
2.1 多层限流策略
经验要点:限流要分层,越靠近用户越好。
场景:电商大促,QPS峰值从平时的1000飙升至100万。
架构设计:
用户请求
↓
CDN限流(静态资源)
↓
API网关限流(用户级限流)
↓
服务限流(接口级限流)
↓
资源限流(数据库/缓存)
Java实现(令牌桶算法):
java
@Component
public class RateLimiter {
private final Map<String, GuavaRateLimiter> limiters = new ConcurrentHashMap<>();
public boolean tryAcquire(String key, int permitsPerSecond) {
GuavaRateLimiter limiter = limiters.computeIfAbsent(
key,
k -> GuavaRateLimiter.create(permitsPerSecond)
);
return limiter.tryAcquire();
}
}
// 使用示例
@GetMapping("/order/create")
public Result createOrder(@RequestParam Long userId) {
// 用户级限流:每用户每秒最多5次请求
if (!rateLimiter.tryAcquire("user:" + userId, 5)) {
return Result.error("请求过于频繁");
}
// 业务逻辑...
}
关键点:
- 用户级限流:防止恶意刷单
- 接口级限流:保护核心接口
- 资源级限流:保护数据库等稀缺资源
2.2 服务降级与熔断
场景:支付服务依赖第三方接口,第三方响应超时导致订单服务阻塞。
设计模式:使用Hystrix或Resilience4j实现熔断降级。
Java实现(Resilience4j):
java
@Configuration
public class ResilienceConfig {
@Bean
public CircuitBreaker paymentCircuitBreaker() {
return CircuitBreaker.ofDefaults("paymentService");
}
@Bean
public TimeLimiter paymentTimeLimiter() {
return TimeLimiter.ofDefaults("paymentService");
}
}
@Service
public class PaymentService {
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
@TimeLimiter(name = "paymentService")
@Retry(name = "paymentService")
public CompletableFuture<PaymentResult> pay(Order order) {
// 调用第三方支付接口
return CompletableFuture.supplyAsync(() ->
thirdPartyPaymentClient.pay(order)
);
}
private CompletableFuture<PaymentResult> fallback(Order order, Exception e) {
// 降级逻辑:记录订单,稍后重试
return CompletableFuture.completedFuture(
PaymentResult.pending(order.getId())
);
}
}
降级策略:
- 自动降级:超时、失败率达到阈值自动触发
- 手动降级:大促期间主动降级非核心功能
- 分级降级:核心功能保留,非核心功能降级
常见陷阱:
- 限流阈值设置不当 - 过低影响正常业务,过高无法保护系统
- 降级逻辑未测试 - 真正降级时发现降级逻辑有bug
- 熔断恢复策略缺失 - 熔断后无法自动恢复,导致服务长期不可用
- 限流粒度太粗 - 全局限流导致正常用户受影响
- 降级未通知用户 - 用户不知道功能已降级,体验差
技术选型建议:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单机限流 | Guava RateLimiter | 轻量级,无需外部依赖 |
| 分布式限流 | Redis + Lua | 支持集群,性能好 |
| API网关限流 | Sentinel / Hystrix | 功能完善,易于集成 |
| 服务熔断 | Resilience4j | 轻量级,性能优异 |
三、缓存策略:多级缓存体系
3.1 缓存架构设计
经验要点:缓存要分层,每层解决不同问题。
架构设计:
浏览器缓存(静态资源)
↓
CDN缓存(边缘节点)
↓
应用本地缓存(Caffeine/Guava)
↓
分布式缓存(Redis Cluster)
↓
数据库(MySQL)
3.2 缓存穿透解决方案
场景:恶意请求不存在的商品ID,直接打到数据库。
方案一:布隆过滤器
java
@Service
public class ProductService {
@Autowired
private BloomFilter<Long> productBloomFilter;
@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Autowired
private ProductMapper productMapper;
public Product getProduct(Long id) {
// 1. 布隆过滤器快速判断
if (!productBloomFilter.mightContain(id)) {
throw new ProductNotFoundException(id);
}
// 2. 查询Redis缓存
String cacheKey = "product:" + id;
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 3. 查询数据库
product = productMapper.selectById(id);
if (product != null) {
// 4. 写入缓存,设置过期时间
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
return product;
}
// 5. 缓存空值,防止缓存穿透
redisTemplate.opsForValue().set(cacheKey, NULL_PRODUCT, 5, TimeUnit.MINUTES);
throw new ProductNotFoundException(id);
}
}
方案二:缓存空值
java
private static final Product NULL_PRODUCT = new Product();
public Product getProduct(Long id) {
String cacheKey = "product:" + id;
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product == NULL_PRODUCT) {
throw new ProductNotFoundException(id);
}
if (product != null) {
return product;
}
product = productMapper.selectById(id);
if (product == null) {
redisTemplate.opsForValue().set(cacheKey, NULL_PRODUCT, 5, TimeUnit.MINUTES);
throw new ProductNotFoundException(id);
}
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
return product;
}
3.3 缓存击穿解决方案
场景:热点商品缓存过期瞬间,大量请求同时打到数据库。
方案一:互斥锁
java
public Product getProductWithLock(Long id) {
String cacheKey = "product:" + id;
String lockKey = "lock:product:" + id;
// 1. 查询缓存
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 2. 获取分布式锁
try {
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
// 3. 双重检查
product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
// 4. 查询数据库
product = productMapper.selectById(id);
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
return product;
} else {
// 5. 未获取锁,短暂等待后重试
Thread.sleep(100);
return getProductWithLock(id);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} finally {
redisTemplate.delete(lockKey);
}
}
方案二:逻辑过期
java
@Data
public class CacheData<T> {
private T data;
private long expireTime; // 逻辑过期时间
}
public Product getProductWithLogicalExpire(Long id) {
String cacheKey = "product:" + id;
// 1. 查询缓存
CacheData<Product> cacheData = redisTemplate.opsForValue().get(cacheKey);
if (cacheData == null) {
// 2. 缓存未命中,返回空商品
return null;
}
// 3. 检查逻辑过期时间
if (cacheData.getExpireTime() > System.currentTimeMillis()) {
// 4. 未过期,直接返回
return cacheData.getData();
}
// 5. 已过期,异步刷新缓存
CompletableFuture.runAsync(() -> {
refreshCache(id);
});
// 6. 返回旧数据
return cacheData.getData();
}
private void refreshCache(Long id) {
Product product = productMapper.selectById(id);
CacheData<Product> cacheData = new CacheData<>();
cacheData.setData(product);
cacheData.setExpireTime(System.currentTimeMillis() + 30 * 60 * 1000);
String cacheKey = "product:" + id;
redisTemplate.opsForValue().set(cacheKey, cacheData, 1, TimeUnit.HOURS);
}
3.4 缓存雪崩解决方案
场景:大量缓存同时过期,数据库压力激增。
方案一:随机过期时间
java
public void setProductToCache(Long id, Product product) {
String cacheKey = "product:" + id;
// 基础过期时间30分钟 + 随机0-5分钟
long expireTime = 30 + ThreadLocalRandom.current().nextInt(5);
redisTemplate.opsForValue().set(cacheKey, product, expireTime, TimeUnit.MINUTES);
}
方案二:缓存预热
java
@Component
public class CacheWarmup {
@Autowired
private ProductService productService;
@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void warmupCache() {
log.info("开始缓存预热...");
// 预热热门商品
List<Long> hotProductIds = getHotProductIds();
hotProductIds.parallelStream().forEach(id -> {
Product product = productService.getProductFromDB(id);
String cacheKey = "product:" + id;
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
});
log.info("缓存预热完成,共预热{}个商品", hotProductIds.size());
}
}
常见陷阱:
- 缓存过期时间设置不当 - 过短导致频繁穿透,过长导致数据不一致
- 缓存更新策略错误 - 先删缓存再更新数据库,导致并发读取到旧数据
- 大Key问题 - 缓存单个对象过大,导致网络传输慢
- 缓存热点问题 - 某些Key访问过于频繁,导致单机压力过大
- 缓存依赖未考虑 - 缓存A依赖缓存B,B失效导致A也失效
性能对比:
| 方案 | QPS | P99延迟 | 命中率 |
|---|---|---|---|
| 无缓存 | 1,000 | 800ms | - |
| 单级缓存 | 10,000 | 150ms | 80% |
| 多级缓存 | 50,000 | 50ms | 95% |
| 提升倍数 | 50x | 16x | - |
技术选型建议:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 本地缓存 | Caffeine | 性能最优,支持自动过期 |
| 分布式缓存 | Redis Cluster | 高可用,支持数据持久化 |
| 缓存穿透防护 | 布隆过滤器 | 内存占用小,查询快 |
| 缓存击穿防护 | 互斥锁 | 简单有效,易于实现 |
四、异步处理:解耦与削峰
4.1 消息队列架构
经验要点:异步处理要考虑消息可靠性、顺序性和幂等性。
架构设计:
生产者
↓
消息队列(Kafka/RocketMQ)
↓
消费者集群
↓
下游服务
4.2 秒杀系统异步处理
场景:秒杀活动,10万商品在1秒内被抢光。
设计思路:前端限流 + 预扣库存 + 异步下单。
Java实现:
java
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Long> redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 秒杀下单
*/
@Transactional
public SeckillResult seckill(Long userId, Long activityId, Long skuId) {
// 1. 检查用户是否已购买
String userKey = "seckill:user:" + activityId + ":" + userId;
Boolean hasBought = redisTemplate.opsForValue().setIfAbsent(userKey, skuId, 24, TimeUnit.HOURS);
if (Boolean.FALSE.equals(hasBought)) {
return SeckillResult.fail("您已购买过该商品");
}
// 2. 预扣库存(Redis原子操作)
String stockKey = "seckill:stock:" + activityId + ":" + skuId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(stockKey);
redisTemplate.delete(userKey);
return SeckillResult.fail("库存不足");
}
// 3. 发送消息到MQ,异步创建订单
SeckillMessage message = new SeckillMessage(userId, activityId, skuId);
rocketMQTemplate.asyncSend("seckill-order-topic", message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("秒杀消息发送成功: {}", message);
}
@Override
public void onException(Throwable e) {
log.error("秒杀消息发送失败: {}", message, e);
// 发送失败,回滚库存
redisTemplate.opsForValue().increment(stockKey);
redisTemplate.delete(userKey);
}
});
return SeckillResult.success("抢购成功,请等待订单创建");
}
}
/**
* 秒杀消息消费者
*/
@Service
@RocketMQMessageListener(
topic = "seckill-order-topic",
consumerGroup = "seckill-order-group"
)
public class SeckillOrderConsumer implements RocketMQListener<SeckillMessage> {
@Autowired
private OrderService orderService;
@Autowired
private RedisTemplate<String, Long> redisTemplate;
@Override
public void onMessage(SeckillMessage message) {
try {
// 1. 幂等性检查
String idempotentKey = "seckill:idempotent:" + message.getUserId() + ":" + message.getSkuId();
Boolean isNew = redisTemplate.opsForValue().setIfAbsent(idempotentKey, "1", 7, TimeUnit.DAYS);
if (Boolean.FALSE.equals(isNew)) {
log.info("重复消息,跳过处理: {}", message);
return;
}
// 2. 创建订单
Order order = orderService.createSeckillOrder(message);
log.info("秒杀订单创建成功: {}", order);
} catch (Exception e) {
log.error("处理秒杀消息失败: {}", message, e);
throw e; // 重试
}
}
}
4.3 延迟队列应用
场景:订单超时自动取消。
Java实现(Redis + ZSet):
java
@Service
public class OrderDelayQueue {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String DELAY_QUEUE_KEY = "order:delay:queue";
private static final String ORDER_PREFIX = "order:";
/**
* 添加订单到延迟队列
*/
public void addOrderToDelayQueue(Long orderId, long delayMinutes) {
long executeTime = System.currentTimeMillis() + delayMinutes * 60 * 1000;
redisTemplate.opsForZSet().add(DELAY_QUEUE_KEY, ORDER_PREFIX + orderId, executeTime);
}
/**
* 扫描延迟队列,处理超时订单
*/
@Scheduled(fixedDelay = 1000) // 每秒执行一次
public void processExpiredOrders() {
long currentTime = System.currentTimeMillis();
// 查询所有到期的订单
Set<String> expiredOrders = redisTemplate.opsForZSet()
.rangeByScore(DELAY_QUEUE_KEY, 0, currentTime);
if (expiredOrders == null || expiredOrders.isEmpty()) {
return;
}
// 处理到期订单
expiredOrders.forEach(orderKey -> {
try {
Long orderId = Long.parseLong(orderKey.substring(ORDER_PREFIX.length()));
// 检查订单状态
Order order = orderService.getOrder(orderId);
if (order != null && order.getStatus() == OrderStatus.PENDING) {
// 取消订单
orderService.cancelOrder(orderId, "超时未支付");
log.info("订单超时取消: {}", orderId);
}
// 从延迟队列中移除
redisTemplate.opsForZSet().remove(DELAY_QUEUE_KEY, orderKey);
} catch (Exception e) {
log.error("处理超时订单失败: {}", orderKey, e);
}
});
}
}
常见陷阱:
- 消息丢失未处理 - 发送失败后没有重试或补偿机制
- 幂等性设计缺失 - 重复消费导致数据重复
- 顺序性未保证 - 需要顺序处理的消息被并发消费
- 死信队列未处理 - 消息消费失败后堆积在死信队列
- 消息积压未监控 - 消费速度跟不上生产速度,导致积压
性能对比:
| 方案 | QPS | P99延迟 | 系统复杂度 |
|---|---|---|---|
| 同步处理 | 5,000 | 800ms | 低 |
| 异步处理 | 50,000 | 150ms | 中 |
| 异步+批量 | 100,000 | 100ms | 高 |
| 提升倍数 | 20x | 8x | - |
技术选型建议:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 高吞吐场景 | Kafka | 吞吐量高,支持分区 |
| 可靠性要求高 | RocketMQ | 支持事务消息,可靠性高 |
| 延迟敏感 | Redis Stream | 延迟低,简单易用 |
| 轻量级场景 | RabbitMQ | 功能完善,易于集成 |
五、数据库优化:读写分离与分库分表
5.1 读写分离架构
场景:电商系统,读多写少,查询压力远大于写入压力。
架构设计:
应用层
↓
读写分离中间件(ShardingSphere/MyCat)
↓
主库(写操作)
↓
从库集群(读操作)
Java实现(ShardingSphere):
yaml
# application.yml
spring:
shardingsphere:
datasource:
names: master,slave1,slave2
master:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://master:3306/db
slave1:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://slave1:3306/db
slave2:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://slave2:3306/db
rules:
readwrite-splitting:
data-sources:
readwrite_ds:
static-strategy:
write-data-source-name: master
read-data-source-names: slave1,slave2
load-balancers:
round-robin:
type: ROUND_ROBIN
代码使用:
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 写操作 - 自动路由到主库
*/
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
}
/**
* 读操作 - 自动路由到从库
*/
public Order getOrder(Long orderId) {
return orderMapper.selectById(orderId);
}
/**
* 强制读主库(保证数据一致性)
*/
@Hint("master")
public Order getOrderFromMaster(Long orderId) {
return orderMapper.selectById(orderId);
}
}
5.2 分库分表策略
场景:订单表数据量超过1亿,查询性能下降。
分片策略:
yaml
spring:
shardingsphere:
rules:
sharding:
tables:
t_order:
actual-data-nodes: db$->{0..1}.t_order_$->{0..3}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db_mod
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: table_mod
sharding-algorithms:
db_mod:
type: MOD
props:
sharding-count: 2
table_mod:
type: MOD
props:
sharding-count: 4
分片算法:
java
/**
* 用户ID分片算法
*/
public class UserIdShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames,
PreciseShardingValue<Long> shardingValue) {
Long userId = shardingValue.getValue();
Long dbIndex = userId % 2; // 分2个库
Long tableIndex = userId % 4; // 每个库分4张表
return "db" + dbIndex + ".t_order_" + tableIndex;
}
}
5.3 分页查询优化
场景:订单列表分页查询,越往后越慢。
优化方案一:游标分页
java
@Data
public class PageResult<T> {
private List<T> records;
private Long nextCursor; // 下一页游标
private Boolean hasNext;
}
@Service
public class OrderQueryService {
/**
* 游标分页查询
*/
public PageResult<Order> queryByCursor(Long userId, Long cursor, int pageSize) {
// 1. 构建查询条件
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getUserId, userId);
if (cursor != null) {
wrapper.lt(Order::getId, cursor);
}
wrapper.orderByDesc(Order::getId);
wrapper.last("LIMIT " + (pageSize + 1)); // 多查一条判断是否有下一页
// 2. 执行查询
List<Order> orders = orderMapper.selectList(wrapper);
// 3. 处理结果
List<Order> result = orders.stream()
.limit(pageSize)
.collect(Collectors.toList());
Long nextCursor = orders.size() > pageSize ?
orders.get(pageSize - 1).getId() : null;
return new PageResult<>(result, nextCursor, nextCursor != null);
}
}
优化方案二:延迟关联
java
/**
* 延迟关联分页查询
*/
public PageResult<OrderVO> queryWithDelayAssociation(Long userId, int pageNum, int pageSize) {
// 1. 先查询ID(只查主键,速度快)
Page<Long> idPage = new Page<>(pageNum, pageSize);
IPage<Long> ids = orderMapper.selectIdsByUserId(userId, idPage);
if (ids.getRecords().isEmpty()) {
return new PageResult<>(Collections.emptyList(), false);
}
// 2. 根据ID批量查询完整数据
List<Order> orders = orderMapper.selectBatchIds(ids.getRecords());
// 3. 转换为VO
List<OrderVO> orderVOs = orders.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
return new PageResult<>(orderVOs, ids.hasNext());
}
常见陷阱:
- 读写分离延迟问题 - 主从复制延迟导致读取到旧数据
- 分片键选择不当 - 导致数据倾斜,某些分片压力过大
- 跨分片查询 - 性能极差,应避免或使用聚合服务
- 分片后事务问题 - 跨分片事务难以保证
- 分片扩展困难 - 分片策略设计不当,后续扩展困难
性能对比:
| 方案 | QPS | P99延迟 | 存储容量 |
|---|---|---|---|
| 单库单表 | 1,000 | 800ms | 1000万 |
| 读写分离 | 5,000 | 300ms | 1000万 |
| 分库分表 | 20,000 | 150ms | 10亿 |
| 提升倍数 | 20x | 5.3x | 1000x |
技术选型建议:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 读写分离 | ShardingSphere | 功能完善,易于集成 |
| 分库分表 | MyCat / ShardingSphere | 支持多种分片策略 |
| 分布式事务 | Seata | 支持AT、TCC、SAGA模式 |
| 数据同步 | Canal | 基于binlog,实时性好 |
六、服务治理:微服务架构
6.1 服务拆分原则
经验要点:按业务领域拆分,避免按技术层次拆分。
拆分维度:
| 拆分维度 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| 业务领域 | 订单服务、商品服务、用户服务 | 职责清晰、独立演进 | 拆分粒度难把握 |
| 数据归属 | 按数据表归属拆分 | 数据一致性易保证 | 可能出现跨服务查询 |
| 读写分离 | 读服务、写服务 | 性能优化明确 | 增加系统复杂度 |
6.2 服务调用优化
场景:订单详情页需要调用多个服务获取数据。
优化方案一:并行调用
java
@Service
public class OrderDetailService {
@Autowired
private OrderService orderService;
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private LogisticsService logisticsService;
/**
* 并行调用多个服务
*/
public OrderDetailVO getOrderDetail(Long orderId) {
// 1. 查询订单
Order order = orderService.getOrder(orderId);
// 2. 并行查询关联数据
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(
() -> userService.getUser(order.getUserId()),
executor
);
CompletableFuture<Product> productFuture = CompletableFuture.supplyAsync(
() -> productService.getProduct(order.getSkuId()),
executor
);
CompletableFuture<Logistics> logisticsFuture = CompletableFuture.supplyAsync(
() -> logisticsService.getLogistics(orderId),
executor
);
// 3. 等待所有结果
CompletableFuture.allOf(userFuture, productFuture, logisticsFuture).join();
// 4. 组装结果
OrderDetailVO vo = new OrderDetailVO();
vo.setOrder(order);
vo.setUser(userFuture.get());
vo.setProduct(productFuture.get());
vo.setLogistics(logisticsFuture.get());
return vo;
}
}
优化方案二:数据聚合服务
java
/**
* 订单聚合服务
*/
@Service
public class OrderAggregateService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private ProductRepository productRepository;
/**
* 聚合查询订单详情
*/
public OrderDetailVO getAggregateOrderDetail(Long orderId) {
// 1. 查询订单
Order order = orderRepository.findById(orderId);
// 2. 批量查询关联数据
List<Long> userIds = Collections.singletonList(order.getUserId());
List<Long> skuIds = Collections.singletonList(order.getSkuId());
Map<Long, User> userMap = userRepository.findByIds(userIds);
Map<Long, Product> productMap = productRepository.findByIds(skuIds);
// 3. 组装结果
OrderDetailVO vo = new OrderDetailVO();
vo.setOrder(order);
vo.setUser(userMap.get(order.getUserId()));
vo.setProduct(productMap.get(order.getSkuId()));
return vo;
}
}
6.3 服务网格架构
场景:微服务数量超过100个,服务间调用复杂。
架构设计:
应用服务
↓
Sidecar代理(Envoy)
↓
服务网格(Istio)
↓
其他服务
核心功能:
- 流量管理:灰度发布、蓝绿部署
- 安全:mTLS认证、授权
- 可观测性:链路追踪、指标监控
常见陷阱:
- 服务拆分过细 - 增加运维成本和调用延迟
- 服务间循环依赖 - 导致服务启动失败或死锁
- 服务版本管理混乱 - 多版本共存导致兼容性问题
- 服务监控缺失 - 无法及时发现服务异常
- 配置管理不当 - 配置变更导致服务不可用
性能对比:
| 方案 | 平均响应时间 | 系统复杂度 | 运维成本 |
|---|---|---|---|
| 单体应用 | 200ms | 低 | 低 |
| 微服务 | 300ms | 高 | 高 |
| 微服务+服务网格 | 350ms | 很高 | 很高 |
| 提升倍数 | 1.75x | - | - |
技术选型建议:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 服务注册发现 | Nacos / Consul | 功能完善,易于集成 |
| 服务配置 | Nacos / Apollo | 支持动态配置 |
| 服务网关 | Spring Cloud Gateway | 功能丰富,易于扩展 |
| 服务网格 | Istio | 功能强大,适合大规模 |
七、实战场景:秒杀系统设计
7.1 整体架构
用户
↓
CDN(静态资源)
↓
Nginx(限流)
↓
API网关(用户限流)
↓
秒杀服务
↓
Redis(库存预扣)
↓
MQ(异步下单)
↓
订单服务
↓
数据库
7.2 核心代码实现
java
/**
* 秒杀服务
*/
@Service
public class SeckillService {
@Autowired
private RedisTemplate<String, Long> redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private ActivityService activityService;
/**
* 秒杀入口
*/
@RateLimiter(value = 100, timeout = 1) // 接口限流100QPS
public SeckillResult seckill(Long userId, Long activityId, Long skuId) {
// 1. 活动校验
Activity activity = activityService.getActivity(activityId);
if (activity == null || !activity.isActive()) {
return SeckillResult.fail("活动不存在或已结束");
}
// 2. 用户限流
String userLimitKey = "seckill:limit:" + activityId + ":" + userId;
Long count = redisTemplate.opsForValue().increment(userLimitKey);
if (count != null && count == 1) {
redisTemplate.expire(userLimitKey, 1, TimeUnit.SECONDS);
}
if (count != null && count > 1) {
return SeckillResult.fail("请求过于频繁");
}
// 3. 预扣库存
String stockKey = "seckill:stock:" + activityId + ":" + skuId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
// 库存不足
redisTemplate.opsForValue().increment(stockKey);
return SeckillResult.fail("库存不足");
}
// 4. 发送消息到MQ
SeckillMessage message = new SeckillMessage(userId, activityId, skuId);
rocketMQTemplate.syncSend("seckill-order-topic", message);
return SeckillResult.success("抢购成功");
}
}
/**
* 秒杀消息消费者
*/
@Service
@RocketMQMessageListener(
topic = "seckill-order-topic",
consumerGroup = "seckill-order-group",
consumeMode = ConsumeMode.ORDERLY // 顺序消费
)
public class SeckillOrderConsumer implements RocketMQListener<SeckillMessage> {
@Autowired
private OrderService orderService;
@Autowired
private InventoryService inventoryService;
@Override
public void onMessage(SeckillMessage message) {
try {
// 1. 幂等性检查
if (orderService.exists(message.getUserId(), message.getSkuId())) {
log.info("订单已存在,跳过: {}", message);
return;
}
// 2. 创建订单
Order order = orderService.createOrder(message);
// 3. 扣减真实库存
inventoryService.deductInventory(message.getSkuId(), 1);
log.info("秒杀订单创建成功: {}", order);
} catch (Exception e) {
log.error("处理秒杀消息失败: {}", message, e);
// 重试
throw e;
}
}
}
7.3 性能优化总结
| 优化点 | 优化手段 | 效果 |
|---|---|---|
| 前端优化 | 静态资源CDN、页面静态化 | 减少服务器压力 |
| 流量控制 | 多层限流、排队系统 | 保护系统稳定性 |
| 库存管理 | Redis预扣、异步确认 | 提升吞吐量 |
| 订单创建 | 消息队列、异步处理 | 解耦削峰 |
| 数据一致性 | 幂等性设计、补偿机制 | 保证数据准确 |
7.5 常见设计模式
模式一:生产者-消费者模式
场景:订单创建后需要发送通知、积分、物流等多个下游服务。
Java实现:
java
/**
* 订单事件发布器
*/
@Service
public class OrderEventPublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 发布订单创建事件
*/
public void publishOrderCreated(Order order) {
OrderCreatedEvent event = new OrderCreatedEvent(order);
eventPublisher.publishEvent(event);
}
}
/**
* 通知服务监听器
*/
@Component
public class NotificationListener {
@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
// 发送通知
notificationService.send(event.getOrder());
}
}
/**
* 积分服务监听器
*/
@Component
public class PointsListener {
@EventListener
@Async
public void handleOrderCreated(OrderCreatedEvent event) {
// 增加积分
pointsService.add(event.getOrder());
}
}
模式二:CQRS(命令查询职责分离)
场景:订单查询和写入的优化需求不同。
架构设计:
命令端(写)
↓
订单服务
↓
事件总线
↓
查询端(读)
↓
订单查询服务
↓
读模型(ES/Redis)
Java实现:
java
/**
* 命令端:创建订单
*/
@Service
public class OrderCommandService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 创建订单命令
*/
@Transactional
public Order createOrder(CreateOrderCommand command) {
// 1. 创建订单
Order order = new Order(command);
orderRepository.save(order);
// 2. 发布事件
OrderCreatedEvent event = new OrderCreatedEvent(order);
eventPublisher.publishEvent(event);
return order;
}
}
/**
* 查询端:查询订单
*/
@Service
public class OrderQueryService {
@Autowired
private OrderReadRepository readRepository;
/**
* 查询订单
*/
public OrderVO getOrder(Long orderId) {
return readRepository.findById(orderId);
}
/**
* 查询订单列表
*/
public Page<OrderVO> queryOrders(OrderQuery query) {
return readRepository.query(query);
}
}
/**
* 事件处理器:更新读模型
*/
@Component
public class OrderReadModelUpdater {
@Autowired
private OrderReadRepository readRepository;
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 更新读模型
OrderVO vo = convertToVO(event.getOrder());
readRepository.save(vo);
}
}
模式三:Saga模式
场景:跨服务的长事务,如订单+库存+支付+物流。
Java实现:
java
/**
* 订单Saga编排器
*/
@Service
public class OrderSagaOrchestrator {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Autowired
private LogisticsService logisticsService;
/**
* 执行订单Saga
*/
@Saga
public void executeOrderSaga(Order order) {
try {
// 1. 扣减库存
inventoryService.deduct(order.getSkuId(), order.getQuantity());
// 2. 创建支付
Payment payment = paymentService.create(order);
// 3. 创建物流
Logistics logistics = logisticsService.create(order, payment);
// 4. 更新订单状态
order.setStatus(OrderStatus.COMPLETED);
orderRepository.save(order);
} catch (Exception e) {
// 补偿操作
compensate(order, e);
throw e;
}
}
/**
* 补偿操作
*/
private void compensate(Order order, Exception e) {
try {
// 回滚库存
inventoryService.rollback(order.getSkuId(), order.getQuantity());
// 取消支付
paymentService.cancel(order.getPaymentId());
// 取消物流
logisticsService.cancel(order.getLogisticsId());
// 更新订单状态
order.setStatus(OrderStatus.FAILED);
orderRepository.save(order);
} catch (Exception ex) {
log.error("补偿操作失败: {}", order, ex);
// 记录失败,人工介入
}
}
}
7.4 压测方案与容量规划
场景:秒杀系统上线前,需要验证系统承载能力。
压测工具选择:
| 工具 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JMeter | 功能强大,图形界面 | 资源占用高 | 功能测试 |
| Gatling | 性能好,脚本简洁 | 学习曲线陡 | 性能测试 |
| Locust | 分布式压测,Python编写 | 需要编程能力 | 大规模压测 |
| wrk/ab | 轻量级,命令行 | 功能简单 | 快速验证 |
压测流程:
java
/**
* 压测脚本示例(JMeter + Java)
*/
public class SeckillLoadTest {
private static final String BASE_URL = "http://seckill-service";
private static final int THREAD_COUNT = 100;
private static final int LOOP_COUNT = 100;
@Test
public void testSeckillPerformance() {
// 1. 准备测试数据
List<Long> userIds = prepareTestUsers(THREAD_COUNT * LOOP_COUNT);
Long activityId = 1L;
Long skuId = 1001L;
// 2. 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT * LOOP_COUNT);
// 3. 记录开始时间
long startTime = System.currentTimeMillis();
// 4. 并发执行
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadIndex = i;
executor.submit(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
try {
Long userId = userIds.get(threadIndex * LOOP_COUNT + j);
SeckillResult result = seckill(userId, activityId, skuId);
// 记录结果
recordResult(result);
} catch (Exception e) {
recordError(e);
} finally {
latch.countDown();
}
}
});
}
// 5. 等待所有请求完成
latch.await();
// 6. 记录结束时间
long endTime = System.currentTimeMillis();
// 7. 输出压测报告
printReport(startTime, endTime);
}
private SeckillResult seckill(Long userId, Long activityId, Long skuId) {
// 模拟HTTP请求
RestTemplate restTemplate = new RestTemplate();
String url = BASE_URL + "/seckill?userId=" + userId +
"&activityId=" + activityId + "&skuId=" + skuId;
return restTemplate.getForObject(url, SeckillResult.class);
}
}
容量规划方法:
java
/**
* 容量规划工具
*/
@Component
public class CapacityPlanner {
/**
* 计算所需实例数
*
* @param expectedQPS 预期QPS
* @param singleInstanceQPS 单实例QPS
* @param redundancyRatio 冗余比例(建议1.5-2.0)
* @return 所需实例数
*/
public int calculateRequiredInstances(int expectedQPS, int singleInstanceQPS, double redundancyRatio) {
double required = (double) expectedQPS / singleInstanceQPS * redundancyRatio;
return (int) Math.ceil(required);
}
/**
* 计算数据库连接池大小
*
* @param expectedQPS 预期QPS
* @param avgQueryTime 平均查询时间(毫秒)
* @param maxConnections 单数据库最大连接数
* @return 连接池大小
*/
public int calculatePoolSize(int expectedQPS, int avgQueryTime, int maxConnections) {
double poolSize = (double) expectedQPS * avgQueryTime / 1000;
return (int) Math.min(Math.ceil(poolSize), maxConnections);
}
/**
* 计算Redis内存需求
*
* @param keyCount Key数量
* @param avgValueSize 平均Value大小(字节)
* @param redundancyRatio 冗余比例(建议1.2)
* @return 所需内存(GB)
*/
public double calculateRedisMemory(int keyCount, int avgValueSize, double redundancyRatio) {
double totalBytes = keyCount * avgValueSize * redundancyRatio;
return totalBytes / (1024 * 1024 * 1024);
}
}
压测报告模板:
markdown
# 秒杀系统压测报告
## 测试环境
- 测试时间:2024-01-15 14:00-16:00
- 测试工具:JMeter 5.5
- 并发线程:100
- 循环次数:100
- 总请求数:10,000
## 测试结果
| 指标 | 目标值 | 实际值 | 是否达标 |
|------|-------|-------|---------|
| QPS | 10,000 | 12,500 | ✅ |
| P99延迟 | 200ms | 150ms | ✅ |
| P95延迟 | 100ms | 80ms | ✅ |
| 平均延迟 | 50ms | 40ms | ✅ |
| 错误率 | 0.1% | 0.05% | ✅ |
## 瓶颈分析
1. 数据库连接池:达到上限,需要扩容
2. Redis内存:使用率80%,建议增加内存
3. 网络带宽:使用率60%,暂不扩容
## 优化建议
1. 增加数据库从库,提升读能力
2. 优化Redis缓存策略,减少内存占用
3. 增加应用实例,提升并发能力
## 容量规划
- 预期峰值QPS:100,000
- 单实例QPS:1,000
- 所需实例数:100(冗余系数1.5)
- 当前实例数:20
- 需要扩容:80个实例
八、监控与运维
8.1 全链路追踪
Java实现(SkyWalking):
java
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/order/{id}")
@NewSpan("getOrder") // 自定义Span
public Order getOrder(@PathVariable Long id) {
return orderService.getOrder(id);
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@NewSpan("queryOrderFromDB")
public Order getOrder(Long id) {
return orderMapper.selectById(id);
}
}
8.2 指标监控
Java实现(Micrometer):
java
@Service
public class OrderMetrics {
private final Counter orderCreatedCounter;
private final Timer orderCreationTimer;
private final Gauge pendingOrderGauge;
public OrderMetrics(MeterRegistry registry) {
this.orderCreatedCounter = Counter.builder("order.created")
.description("订单创建总数")
.register(registry);
this.orderCreationTimer = Timer.builder("order.creation.time")
.description("订单创建耗时")
.register(registry);
this.pendingOrderGauge = Gauge.builder("order.pending.count",
this, OrderMetrics::getPendingOrderCount)
.description("待处理订单数")
.register(registry);
}
public void recordOrderCreated() {
orderCreatedCounter.increment();
}
public Timer.Sample startOrderCreation() {
return Timer.start();
}
public void stopOrderCreation(Timer.Sample sample) {
sample.stop(orderCreationTimer);
}
private double getPendingOrderCount() {
return orderService.getPendingOrderCount();
}
}
8.3 告警规则
Prometheus告警规则:
yaml
groups:
- name: order_alerts
interval: 30s
rules:
# 订单创建失败率过高
- alert: HighOrderFailureRate
expr: |
rate(order_created_failed_total[5m]) /
rate(order_created_total[5m]) > 0.1
for: 2m
labels:
severity: critical
annotations:
summary: "订单创建失败率过高"
description: "订单创建失败率超过10%"
# 订单处理延迟过高
- alert: HighOrderProcessingLatency
expr: |
histogram_quantile(0.99,
rate(order_creation_time_seconds_bucket[5m])) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "订单处理延迟过高"
description: "P99订单处理延迟超过2秒"
# 待处理订单积压
- alert: PendingOrderBacklog
expr: order_pending_count > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "待处理订单积压"
description: "待处理订单数超过10000"
九、总结与行动建议
核心设计原则
- 分层限流:从CDN到数据库,层层设防
- 多级缓存:本地缓存 + 分布式缓存 + 数据库
- 异步解耦:消息队列处理非实时业务
- 读写分离:读多写少场景下的性能优化
- 服务治理:熔断降级保证系统稳定性
实战检查清单
在做高并发系统设计时,建议回答以下问题:
- 流量峰值是多少?是否有突发流量?
- 哪些是核心接口?需要重点保护?
- 缓存策略是什么?如何解决缓存穿透/击穿/雪崩?
- 是否需要异步处理?消息队列如何保证可靠性?
- 数据库如何优化?是否需要读写分离或分库分表?
- 如何保证数据一致性?是否有补偿机制?
- 监控指标有哪些?告警规则是否完善?
行动建议
从你负责的系统中选一个高并发场景,按照本文的方法论进行优化:
- 分析现状:梳理流量、数据、服务三个维度
- 设计方案:制定限流、缓存、异步等优化策略
- 逐步实施:先优化核心路径,再完善周边系统
- 监控验证:通过指标验证优化效果