高并发接口优化
「接口访问过多,导致响应慢该怎么做?」——面试官最看重思路的分层,不是背一串名词。
第一步:先定位,不要瞎猜
- 看监控:QPS、RT 曲线,确定是单个接口慢还是整体慢;
- 链路追踪(SkyWalking)找最慢环节:DB?外部调用?锁竞争?
- 慢 SQL 日志 + 线程 dump,拿到证据再动手。
缓存:让大部分请求不打到数据库(优先级最高)
请求链路:浏览器缓存 → CDN → 网关缓存 → 应用本地缓存(Caffeine) → Redis → DB
- 读多写少的数据(商品详情、配置、热搜榜)放 Redis,TTL + 随机退避防雪崩;
- 热点 key 用本地缓存兜一层(注意一致性:写后失效);
- 缓存命中率 ≥ 90% 时,数据库压力直接降一个量级。
限流:超过容量的流量直接拒绝 / 排队
| 层级 | 手段 |
|---|---|
| Nginx | limit_req_zone 按 IP / URI 限速 |
| 网关层 | Sentinel / Spring Cloud Gateway + Redis:接口级 QPS 限流 |
| 业务层 | 注解式限流 @RateLimit(key = "'login:' + #ip", qps = 5")(Redis + Lua 令牌桶) |
| 防刷 | 验证码、同一账号登录失败 5 次锁定 10 分钟 |
Redis + Lua 令牌桶核心思路(原子执行,避免超发):
// KEYS[1]=限流key, ARGV: 容量/速率/当前时间 → 返回 1 放行 / 0 拒绝
Long allow = redisTemplate.execute(RATE_LIMIT_SCRIPT, keys, capacity, rate, now);
if (allow == 0) {
throw new BizException(42900, "请求过于频繁,请稍后再试");
}
异步 & 削峰:请求处理路径越短越快
- 非核心逻辑异步化:发通知、记日志、埋点 →
@Async线程池或 MQ,接口耗时只保留主链路; - 写请求削峰:秒杀 / 下单类场景请求先进 MQ 排队,消费端按数据库能承受的速率处理,用户端轮询或推送结果;
- 并行调用:接口内多个无依赖的下游调用用
CompletableFuture.allOf并行,总耗时 = 最慢者。
数据库层:慢 SQL 是接口慢的常见根因
- 慢查询日志 +
EXPLAIN逐条治理(索引、深分页、大事务); - 连接池调优(HikariCP:
maximumPoolSize不要盲目调大); - 读写分离分摊读压力。
扩容与兜底
- 水平扩容:服务无状态化 → 加机器 + 负载均衡,容量线性提升;
- 熔断降级:依赖的下游故障时快速失败(Sentinel / Resilience4j),非核心功能降级返回默认值,防止故障雪崩;
- 事前:压测(JMeter / wrk)摸清容量水位,提前扩容 + 预热缓存;
- 事后:监控告警(Prometheus + Grafana),加链路追踪留证据。
:::tip 一句话收尾(面试用) 单接口慢先看慢 SQL 和外部调用;整体慢优先加缓存;流量超容量用限流保护自己;写入洪峰用 MQ 异步削峰;还扛不住就无状态扩容;极端情况降级保主链路。核心思想:每一次流量增长,都别让数据库独自硬扛。 :::