高并发系统设计实战指南

高并发系统设计实战指南

高并发系统设计是架构师的必修课,也是大厂面试的高级话题,CSDN 架构板块的流量之王。本文从性能指标讲起,覆盖分层架构、缓存体系、消息削峰、数据库扩展、读写分离、分库分表、限流降级、全链路压测,最后用一个电商秒杀案例完整串起来。


一、高并发的度量指标

1.1 核心指标

指标 含义 参考值
QPS 每秒查询数(读) 单机几百~几千
TPS 每秒事务数(写) 单机几百
并发数 同时处理的请求数 与线程池/连接池相关
响应时间 单请求耗时 p99 < 200ms 算好
可用性 系统可用时间占比 99.9% ~ 99.99%

QPS 计算:一天 1000 万 PV,高峰在 2 小时,高峰 QPS ≈ 1000万×0.8 / (2×3600) ≈ 1100。

性能指标看 p99 不看平均值:平均响应时间会被少数慢请求拉偏,p99(99% 的请求小于该值)才代表真实体验。

1.2 高并发本质

高并发不是"一台机器很牛",而是把压力分散:

复制代码
单机 1000 QPS → 10 台机器 → 10000 QPS

核心思路:无状态化(任何一台机器都能处理任何请求)+ 水平扩展(加机器)+ 分层防御(缓存扛读、MQ 扛写)。


二、分层架构

2.1 经典五层

复制代码
用户 → CDN(静态资源加速)
  → 负载均衡(Nginx/LB)
  → 网关(鉴权、限流)
  → 应用层(无状态服务集群)
  → 缓存层(Redis)
  → 数据层(MySQL + 分库分表 + 读写分离)

每一层的作用:

  • CDN:静态资源(图片/JS)就近返回,省源站流量;
  • 负载均衡:流量分发,健康检查;
  • 网关:统一入口,限流鉴权;
  • 应用层 :业务逻辑,必须无状态(Session 放 Redis);
  • 缓存:扛 80% 的读;
  • 数据层:最终一致性,扛 20% 的读写。

2.2 无状态设计(关键)

有状态的问题:用户登录信息存内存,用户下次请求到另一台机器就"掉线"了。

解决方案:状态外置。

复制代码
Session → Redis(共享)
Token(JWT)→ 客户端保存,服务端无状态
yaml 复制代码
# Spring Session 存 Redis
spring:
  session:
    store-type: redis

无状态的好处:随便加机器、机器随便挂、流量随便切。


三、缓存体系(读多写少的核心)

3.1 多级缓存

复制代码
本地缓存(Caffeine,毫秒级,每台机器一份)
  → Redis(分布式缓存,统一)
  → 数据库(兜底)

为什么多级:本地缓存不经过网络(最快),Redis 统一共享(一致性好),数据库保底。

3.2 缓存三大问题(必考)

穿透(查不存在的 key):

java 复制代码
// 方案:缓存空值 + 布隆过滤器
if (bloomFilter.mightContain(key)) {
    // 才允许查库
}

击穿(热点 key 过期瞬间):

java 复制代码
// 方案:互斥锁,只放一个线程查库
String lockKey = "lock:" + key;
boolean locked = redis.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) { waitAndRetry(); return; }
try { /* 查库 + 重建缓存 */ } finally { redis.delete(lockKey); }

雪崩(大量 key 同时过期):

java 复制代码
// 方案:过期时间加随机值
int expire = 3600 + ThreadLocalRandom.current().nextInt(600);
redis.set(key, value, expire, TimeUnit.SECONDS);

3.3 缓存一致性

**Cache-Aside(旁路缓存)**标准做法:

复制代码
读:先查缓存 → 没有查库 → 回填缓存
写:先更新数据库 → 再删缓存(不是更新缓存!)
java 复制代码
@Transactional
public void updateUser(User user) {
    userMapper.update(user);         // 1. 先更新数据库
    redis.delete("user:" + user.id); // 2. 再删缓存
}

为什么删缓存而不是更新缓存:更新缓存有并发问题(两个线程同时写),删缓存让下次读时重建,简单可靠。

极端一致:用 Canal 监听 binlog 异步删缓存,或消息队列补偿。


四、消息队列削峰(写多场景)

4.1 削峰原理

复制代码
高峰 10 万请求/s
  ↓(MQ 缓冲)
消费端按 1000/s 处理

典型场景:秒杀、下单、积分、异步通知。

4.2 秒杀下单异步化

复制代码
用户请求 → 网关限流 → 秒杀接口(只写 Redis 预扣)
  → 发 MQ「秒杀成功」
  → 消费者异步:真实扣库存 + 创建订单 + 通知用户

好处:接口毫秒级返回(抢到没抢到立刻知道),数据库不被瞬间打爆。

4.3 消息可靠性(三环节)

复制代码
生产:acks=all + 重试 + 幂等
Broker:多副本(replication-factor=2)
消费:手动提交 + 业务幂等(唯一键去重)

五、数据库扩展

5.1 读写分离

复制代码
写 → 主库(1 个)
读 → 从库(多个,binlog 同步)

适用:读多写少(80% 读)。应用层用 ShardingSphere/MyCat 或代码路由。

注意:

  • 主从有同步延迟(秒级)------刚写完立刻读可能读不到("写后读"问题);
  • 解决:写后读强制走主库,或从库读失败回主库重试。

5.2 分库分表

数据量到千万/亿级,单表扛不住:

复制代码
分库:按业务拆库(订单库/用户库/商品库)
分表:单库单表拆多表
  - 水平分表:按 ID 取模 / 范围 / 一致性哈希
sql 复制代码
-- 订单表按 user_id 分 16 张表
order_0, order_1, ..., order_15
-- 路由规则:user_id % 16

分片键选择(关键):

  • 按高频查询条件选分片键(订单按 user_id 分,查用户订单直接路由);
  • 分片键之外的查询变"全分片扫"(要维护映射表或 ES 辅助);
  • 跨分片 JOIN 不行 → 冗余字段/聚合查询。

分库分表带来的新问题:

  • 全局主键(雪花 ID、美团 Leaf);
  • 跨分片分页(归并排序);
  • 分布式事务(最终一致)。

5.3 什么时候该分

复制代码
单表 < 500 万 → 索引优化
500 万 ~ 2000 万 → 读写分离 + 缓存
2000 万+ → 分库分表

经验:先缓存、先读写分离,最后才分库分表------分库分表是最后的武器。


六、限流与降级

6.1 限流算法

算法 原理 特点
固定窗口 每秒计数 简单,临界问题
滑动窗口 窗口细分 更平滑
令牌桶 匀速放令牌 允许突发,主流(Guava RateLimiter)
漏桶 匀速出 强控速

Sentinel 限流(前面文章已覆盖):QPS 阈值 + 排队等待/直接拒绝。

6.2 降级与熔断

复制代码
熔断:错误率超阈值 → 快速失败(不继续调下游)
降级:走兜底逻辑(返回默认值/提示)
隔离:线程池隔离(一个下游满不拖垮其他)
java 复制代码
@SentinelResource(value = "getProduct", fallback = "productFallback")
public Product getProduct(Long id) {
    return productClient.getProduct(id);
}

public Product productFallback(Long id, Throwable e) {
    return Product.empty("商品服务暂时不可用");
}

6.3 系统保护三板斧

  • 超时:所有下游调用设超时(连接 3s + 读 5s);
  • 重试:幂等接口重试 1-2 次(带退避);
  • 兜底:缓存/默认值/人工处理。

七、高可用设计

7.1 可用性公式

复制代码
可用性 = 运行时间 / 总时间
99.9% = 一年故障 8.76 小时
99.99% = 一年故障 52 分钟

7.2 关键手段

  • 冗余:每个组件多实例(应用 2+、Redis 主从、MySQL 主从);
  • 故障转移:挂了自动切换(Nacos 摘除、Sentinel 熔断、Keepalived VIP);
  • 优雅上下线:滚动发布、先摘流量再停机;
  • 容灾:多机房、跨区备份。

7.3 幂等设计(高并发必备)

场景:用户点了两次"支付",或超时重试------不能扣两次钱。

方案:

  • 唯一约束:订单号唯一索引;
  • 状态机:只有"待支付"才能支付;
  • 幂等表:请求带 requestId,先查后写;
  • Redis 防重:setIfAbsent("pay:" + orderId, ...)。

八、全链路压测

8.1 为什么要压测

上线前必须知道系统真实极限------别等线上爆了才发现。

8.2 压测流程

复制代码
1. 定目标:预估峰值 QPS(历史数据 × 增长系数)
2. 建环境:独立压测环境(或线上低峰)
3. 造数据:真实规模的数据量(几千万行)
4. 加压:wrk/JMeter/Locust 逐步加压
5. 观察:QPS、响应时间、CPU、连接数
6. 找瓶颈:哪一层先扛不住
7. 优化后再压
bash 复制代码
# wrk 快速压测
wrk -t8 -c200 -d30s http://localhost:8080/api/order

8.3 常见瓶颈排查顺序

复制代码
先看应用层(CPU/线程池满?)
→ 缓存命中率(Redis 打满?)
→ 数据库(慢 SQL?连接池满?)
→ 网络/网关(带宽?Nginx 连接数?)

九、实战:电商秒杀系统设计

9.1 需求与挑战

场景:1 万件商品,10 万人抢------瞬时 QPS 几万。

挑战:

  • 数据库瞬间打爆;
  • 超卖(卖出 1.5 万件);
  • 用户重复请求。

9.2 分层设计

复制代码
用户(App/浏览器)
  → CDN:静态页面
  → Nginx + 网关:限流(IP + 用户 + 总流量)
  → 秒杀接口(应用层)
      ├─ Redis 预扣库存(原子 DECR,防超卖)
      ├─ 校验(每人限购 1 件,Redis Set)
      └─ 发 MQ「秒杀成功」
  → MQ 消费者(异步)
      ├─ 真实扣库存(数据库,幂等)
      ├─ 创建订单
      └─ 通知用户(推送/站内信)

9.3 关键代码片段

Redis 原子扣库存(防超卖):

java 复制代码
// 库存用 Redis 预扣:DECR 返回负数就是没抢到
Long remain = redis.opsForValue().decrement("seckill:stock:" + skuId);
if (remain < 0) {
    redis.opsForValue().increment("seckill:stock:" + skuId);  // 还回去
    return Result.error("已售罄");
}

// 每人限购
Boolean first = redis.setIfAbsent("seckill:user:" + userId, skuId, 60, TimeUnit.SECONDS);
if (!first) {
    return Result.error("每人限购一件");
}

// 发 MQ 异步下单
mq.send("seckill-success", orderDTO);
return Result.ok("抢购成功,正在生成订单");

数据库兜底(最终一致性):

sql 复制代码
-- 数据库扣库存带条件(防超卖的最后防线)
UPDATE product SET stock = stock - 1
WHERE id = #{skuId} AND stock > 0;

MQ 消费幂等:

java 复制代码
// 唯一键防重复消费
INSERT INTO seckill_order(order_id, user_id, sku_id)
VALUES(?, ?, ?) ON DUPLICATE KEY UPDATE order_id = order_id;

9.4 秒杀要点总结

层 手段
前端 静态化、按钮置灰、倒计时
网关 限流(总 QPS + 单用户)
缓存 Redis 预扣库存、限购标记
异步 MQ 削峰,接口毫秒返回
数据库 条件更新防超卖、幂等约束
降级 秒杀失败走兜底、服务保护

十、面试高频题速览

  1. QPS 怎么估算:PV/时间窗/峰值系数;
  2. 缓存三大问题:穿透/击穿/雪崩及方案;
  3. 缓存一致性:Cache-Aside,先更库再删缓存;
  4. 读写分离延迟:写后读强制走主库;
  5. 分库分表键选择:按高频查询条件;
  6. 全局 ID 方案:雪花算法、Leaf;
  7. 防超卖:Redis DECR + 数据库条件更新;
  8. 幂等设计:唯一约束、状态机、幂等表;
  9. 限流算法:令牌桶 vs 漏桶;
  10. 高可用:冗余 + 故障转移 + 幂等 + 降级。

本章小结

高并发设计的核心是分层防御 + 异步削峰 + 数据扩展 :缓存扛读、MQ 扛写、数据库做最终兜底;先无状态化、再水平扩展;先缓存和读写分离、最后才分库分表。记住------能用加机器解决的别用复杂架构,能用缓存解决的别动数据库。

五篇 CSDN 热门主题文档全部完成:Python 数据分析、Spring Cloud Alibaba、JavaScript 进阶、机器学习、高并发系统设计------覆盖数据分析、微服务、前端、AI、架构五大方向。

(全文完)

相关推荐
Sam_Deep_Thinking1 小时前
CountDownLatch实现原理
java·后端·面试·程序员
小狼Solar1 小时前
SAP MDG 功能范围说明(基于S/4HANA 2025)
java·开发语言
范中勤1 小时前
Python 与 Java:模块、包、对象、反射核心差异总结
java·python·面试·反射·元类
ab1237681 小时前
Spring Integration 完整笔记 + 和 spring-integration-mqtt、Paho、EMQX 的关系
java·笔记·spring
小蜗牛狂飙记1 小时前
java环境一键配置和切换的ui工具
java
逆光的July2 小时前
可靠消息投递与消费的工程设计:Outbox、幂等、死信兜底
java
bro_Java6662 小时前
《二叉树的基础知识、操作方法与代码示例》
java·数据结构·算法·广度优先
dishugj2 小时前
HANA集群计划内切换与维护操作
java·linux·数据库
yuniko-n2 小时前
【JUC】线程间通信
java·开发语言