一、先明确高并发接口到底要解决什么问题
高并发接口的核心目标通常有 4 个:
- 扛得住流量
- 响应足够快
- 数据不能乱
- 高峰期系统不能崩
比如电商场景里:
- 秒杀下单接口
- 抢优惠券接口
- 大促查询商品库存接口
- 热门活动报名接口
这些都属于典型高并发接口。
所以我设计时会先问自己一句话:
这个接口是读多写少,还是写多读少?
因为两种设计思路完全不一样。
- 读多写少:重点是缓存、CDN、热点隔离
- 写多读少:重点是限流、削峰、异步、幂等、一致性
二、设计高并发接口的整体思路
先给一个总框架:
高并发接口设计,本质上就是“前面挡住流量,中间快速处理,后面避免阻塞,异常时还能兜住”。
通常我会从这 8 个方面来设计:
- 接口分层与无状态化
- 限流与流量控制
- 缓存与热点处理
- 异步削峰
- 幂等与防重复提交
- 数据一致性控制
- 降级、熔断和隔离
- 监控、压测和容量评估
三、第一步:接口先无状态,方便横向扩容
高并发接口首先要求服务实例可以横向扩容。
应用层可以设计成:
- 无状态服务
- 请求通过 Nginx / Gateway / SLB 打到多个应用节点
- 节点可以随时扩容和缩容
这样高峰期可以快速扩机器。
比如一个抢购接口:
- 10 台服务扛不住,就临时扩到 30 台
- 流量通过负载均衡自动分发
这一步是高并发的基础。
四、第二步:入口限流,先保护系统
高并发接口不能让所有请求都直接打到后端,否则数据库和服务会被冲垮。
所以一定要在入口做限流。
常见限流位置有三层:
1. 网关限流
在 Nginx、Spring Cloud Gateway、OpenResty 这一层限流。
比如:
- 单 IP 每秒最多 50 次请求
- 单用户每秒最多 5 次请求
- 某活动接口每秒最多 1 万次请求
这一步是第一道保护。
2. 应用级限流
在服务内部再做更细颗粒度控制。
例如:
- 按用户ID限流
- 按商品ID限流
- 按接口维度限流
常用算法有:
- 令牌桶
- 漏桶
- 滑动窗口
- 固定窗口
如果面试官追问,我一般会说:
网关层适合粗粒度全局保护,应用层适合业务级精细限流,两层结合效果最好。
3. Redis 分布式限流
如果是多实例部署,本地限流不够,要用 Redis 做全局限流。
比如 Lua + Redis 实现滑动窗口限流。
五、第三步:缓存前置,减少对数据库的冲击
如果这个高并发接口是读接口,比如查询商品详情、查询库存、查询活动信息,那第一选择一定是缓存。
1. Redis 缓存
热点数据直接放 Redis:
- 商品详情
- 活动信息
- 库存余量
- 用户资格信息
接口先查 Redis,只有缓存失效才打数据库。
2. 本地缓存
对于极热点数据,可以再加一层本地缓存,比如 Caffeine。
路径变成:
本地缓存 → Redis → DB
这样能进一步减少 Redis 压力。
3. CDN / 页面静态化
如果是活动页、商品详情页这种大量静态读流量,可以直接:
- 静态化 HTML
- CDN 分发
- 前端缓存
这样大量请求甚至都到不了应用服务。
六、第四步:写接口不能硬扛,要削峰填谷
如果是下单、抢券、报名这种写接口,高并发下不能每个请求都同步落库,否则数据库肯定顶不住。
这时候要做异步化和削峰。
典型做法:MQ 排队
比如抢券接口:
- 用户请求先做资格校验
- 校验通过后,不直接操作数据库
- 请求写入消息队列
- 消费者异步处理发券逻辑
- 用户先拿到“排队中”或“抢券结果稍后通知”
这样做的好处是:
- 把瞬时流量压平
- 保护数据库
- 提升系统稳定性
常用 MQ:
- RocketMQ
- Kafka
- RabbitMQ
如果是交易场景,我更倾向 RocketMQ,因为它在顺序消息、事务消息、延迟消息上比较常见。
七、第五步:幂等设计,避免重复请求把数据打乱
高并发下最常见的问题之一,就是用户重复点击、网络重试、消息重复投递。
所以接口必须做幂等。
常见幂等方案
1. 前端防重
按钮置灰、提交后 loading,这只是第一层。
2. Token 防重
请求前先生成一个唯一 token,提交时校验并删除。
3. Redis setnx
比如:
- 用户 + 业务ID 作为 key
- 用
SETNX抢占 - 成功才允许处理
4. 数据库唯一约束
比如下单接口里:
- 同一个业务单号只能生成一次订单
- 数据库层加唯一索引兜底
5. 消息消费幂等
MQ 消费时,通过业务流水号去重,保证重复消息不会重复处理。
面试里这句很关键:
高并发接口不怕重复请求,怕的是重复请求被重复处理。
八、第六步:热点资源要单独处理
高并发接口最怕“热点”。
比如:
- 一个爆款商品 ID
- 一个秒杀活动
- 一个热门直播间
- 一个抢券模板
这些热点会把流量集中到单个 key、单条数据、单个分区上。
处理方法
1. 热点缓存
把热点数据提前预热到 Redis。
2. 热点隔离
把热点接口、热点商品流量单独隔离处理,不和普通流量混用。
3. 分片/分桶
比如库存不要只放一个 key,可以拆成多个桶,降低并发争抢。
4. 本地缓存兜底
热点信息优先走 JVM 本地缓存。
九、第七步:数据库层一定要减压
高并发接口设计不好,最后都是数据库扛雷。
所以数据库层要尽量减压。
1. 读写分离
读请求走从库,写请求走主库。
2. 分库分表
如果单表数据太大,必须拆。
例如订单、优惠券领取记录、活动参与记录。
3. 批量写
异步场景下尽量批量 insert / update,减少数据库交互次数。
4. 乐观锁/CAS
如果有并发更新,比如库存扣减,可以用:
- version 乐观锁
- 条件更新
例如:
update stock
set available = available - 1
where sku_id = ? and available > 0
如果要更稳,可以加 version 字段。
5. 避免长事务
高并发接口里,事务一定要短,不能查很多表、调很多外部服务后再提交事务。
原则是:
核心写库逻辑放事务内,非核心操作异步化。
十、第八步:高并发写接口要注意一致性
很多人设计高并发接口只讲性能,不讲一致性,这是不够的。
比如下单接口,可能同时涉及:
- 订单服务
- 库存服务
- 支付服务
- 优惠券服务
这时候不能指望一个本地事务搞定,通常是:
- 本地事务 + MQ 最终一致性
- 或者 TCC
- 或者可靠消息 + 补偿机制
如果是普通高并发接口,我一般优先选:
本地事务 + 异步消息 + 定时补偿
因为这个方案在性能和复杂度之间比较平衡。
十一、第九步:一定要有降级、熔断、隔离
高并发接口不是“尽量都处理”,而是“核心链路活着最重要”。
1. 降级
比如下单接口中:
- 推荐商品可不返回
- 个性化标签可关闭
- 非关键埋点异步化
核心链路先保住。
2. 熔断
如果依赖的下游服务异常,比如营销服务超时:
- 可以快速失败
- 或走默认值
- 防止把主链路拖死
3. 隔离
线程池隔离、服务隔离、流量隔离都很重要。
例如:
- 会员接口和下单接口不要共用同一线程池
- 热门活动和普通活动分开处理
十二、第十步:接口返回要分同步结果和异步结果
高并发接口不一定都要同步告诉用户“成功”或“失败”。
很多时候更合理的方式是:
- 先返回“请求已受理”
- 后台异步处理
- 用户通过轮询、回调、站内信查看最终结果
比如:
- 抢券:返回“排队中”
- 秒杀:返回“抢购请求已提交”
- 大促下单:返回“订单处理中”
这样能显著降低接口耗时和系统压力。
十三、如果让我举一个具体案例
比如设计一个高并发抢优惠券接口,我会这样设计:
1. 请求入口
- 网关限流
- 用户登录校验
- 黑名单和风控校验
2. 资格预校验
先查 Redis:
- 券是否存在
- 活动是否开始
- 库存是否还有
- 用户是否已经领过
3. Lua 脚本原子判断
通过 Redis Lua 一次性完成:
- 判断库存
- 判断是否重复领取
- 扣减库存
- 写入领取标记
这样避免多次 Redis 往返,也保证原子性。
4. 异步入队
抢到资格的用户写入 MQ。
5. 消费端落库
消费者异步执行:
- 写领取记录
- 发放优惠券
- 更新用户券包
6. 失败补偿
如果消费失败:
- 重试
- 死信队列
- 人工补偿或自动回滚库存
7. 结果查询
用户可通过查询接口查看最终是否领券成功。
这个方案能很好地支撑高并发,而且相对稳。
十四、面试回答
我会先判断这个接口是读多写少还是写多读少。
如果是读接口,我会优先用多级缓存、本地缓存、Redis 和 CDN 减少对数据库的访问。
如果是写接口,我会重点做限流、削峰、异步化和幂等控制,避免所有请求直接打到数据库。
整体上,我会把服务设计成无状态方便横向扩容,在网关和应用层做多级限流,在 Redis 做热点数据缓存和原子判断,在 MQ 做异步削峰,在数据库层做读写分离、分库分表和短事务控制。
同时,为了保证系统稳定,我还会补充降级、熔断、隔离、监控告警、压测和容量评估。
如果这个接口涉及库存、订单、支付等多个服务,我会采用本地事务加消息最终一致性的方案来保证数据正确。
十五、总结
高并发接口设计的核心不是“让所有请求都进来”,而是:
让真正有效的请求尽快处理,让无效请求尽早拦截,让核心链路始终可用。