线上接口负载满了如何解决
线上接口负载满了如何解决
面试官您好,我会按照 "先止损、再定位、后根治" 的标准线上问题处理流程来解决这个问题,同时结合我过往处理千万级 QPS 接口的经验来展开说明。
📋 整体处理流程图
🚨 第一阶段:紧急止损(5 分钟内必须完成)
这是最关键的一步,先保住系统不雪崩,再谈其他。
- 接口限流:立即在网关层开启限流保护
- 采用令牌桶算法(支持突发流量)
- 先设置一个保守的阈值(如平时峰值的 80%)
- 优先保障核心接口,非核心接口直接限流
- 服务降级:关闭非核心功能
- 关闭推荐、评论、个性化等非核心功能
- 返回兜底数据(如静态页面、缓存数据)
- 关闭写操作,只保留读操作
- 熔断隔离:隔离故障服务
- 对超时率高、错误率高的服务进行熔断
- 使用线程池隔离不同业务模块
- 防止一个服务拖垮整个集群
- 快速扩容:临时增加服务实例
- 利用云平台的弹性伸缩能力快速扩容
- 优先扩容无状态的应用服务
- 数据库和缓存谨慎扩容(避免数据不一致)
- 流量切分:将流量引导到备用集群
- 如果有异地多活架构,切一部分流量到备用机房
- 关闭灰度发布,将所有流量切回稳定版本
🔍 第二阶段:问题定位(15-30 分钟)
止损后,立即开始定位问题根源,为后续优化提供依据。
| 排查维度 | 关键指标 | 常见问题 |
|---|---|---|
| 应用层 | CPU、内存、GC、线程数 | 死循环、内存泄漏、频繁 Full GC、线程阻塞 |
| 网络层 | 带宽、连接数、响应时间 | 网络拥塞、TCP 连接耗尽、慢查询 |
| 数据层 | QPS、连接数、慢 SQL | 索引失效、锁竞争、连接池耗尽 |
| 缓存层 | 命中率、内存使用率 | 缓存穿透、缓存击穿、缓存雪崩 |
- 监控大盘:先看整体指标,再逐步缩小范围
- 日志分析:重点查看错误日志、慢请求日志
- 压测复现:在测试环境模拟线上流量,复现问题
- 工具辅助:使用 Arthas、JProfiler 等工具进行线上诊断
🚀 第三阶段:长期根治(后续优化)
找到问题根源后,进行针对性的优化,从根本上解决负载问题。
1. 代码级优化
- 优化慢方法:将耗时操作异步化、并行化
- 减少循环嵌套:降低时间复杂度
- 避免重复计算:使用局部变量缓存结果
- 优化对象创建:减少不必要的对象创建和销毁
2. 架构级优化
- 服务拆分:将大服务拆分为多个小服务,按业务维度隔离
- 读写分离:读操作走从库,写操作走主库
- 异步化:将非实时操作(如发送短信、记录日志)异步处理
- 消息队列:削峰填谷,解耦系统
3. 数据层优化
- 索引优化:为常用查询添加合适的索引
- SQL 优化:避免 SELECT *、避免大表 JOIN、使用分页查询
- 分库分表:对大表进行水平或垂直拆分
- 连接池优化:合理设置连接池大小,避免连接池耗尽
4. 缓存层优化
- 多级缓存:本地缓存 + 分布式缓存结合
- 缓存预热:提前将热点数据加载到缓存
- 缓存降级:缓存失效时返回兜底数据
- 防止缓存穿透 / 击穿 / 雪崩:使用布隆过滤器、互斥锁、过期时间随机化
💻 核心代码与技术亮点 ✨
1. 网关层限流(Sentinel 动态限流)
技术亮点:支持动态调整阈值,无需重启服务;基于 QPS 和线程数双重限流
@Configuration
public class SentinelConfig {
// 初始化限流规则(实际项目中从Nacos/Apollo配置中心读取)
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
// 核心商品查询接口限流:1000 QPS
FlowRule productRule = new FlowRule();
productRule.setResource("product:query");
productRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
productRule.setCount(1000);
// 限流策略:直接拒绝
productRule.setStrategy(RuleConstant.STRATEGY_DIRECT);
// 流量整形:匀速排队,允许突发流量
productRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
// 最大排队时间:500ms
productRule.setMaxQueueingTimeMs(500);
rules.add(productRule);
FlowRuleManager.loadRules(rules);
}
// 自定义限流异常处理器
@Bean
public BlockExceptionHandler blockExceptionHandler() {
return (exchange, ex) -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String result = JSON.toJSONString(Result.fail("系统繁忙,请稍后再试"));
DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
};
}
}2. 多级缓存实现(Caffeine+Redis)
技术亮点:本地缓存优先,减少 Redis 网络 IO;支持自动过期和刷新;解决缓存击穿问题
@Component
public class ProductCacheService {
// 本地缓存:Caffeine,高性能,低延迟
private final LoadingCache<Long, Product> localCache = Caffeine.newBuilder()
.maximumSize(10000) // 最大缓存10000个商品
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后5分钟过期
.refreshAfterWrite(1, TimeUnit.MINUTES) // 写入后1分钟自动刷新
.build(this::loadProductFromDb);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductMapper productMapper;
// 获取商品:本地缓存 -> Redis -> 数据库
public Product getProduct(Long productId) {
// 1. 先查本地缓存
try {
return localCache.get(productId);
} catch (Exception e) {
log.error("本地缓存获取失败,productId:{}", productId, e);
}
// 2. 本地缓存失败,查Redis
String key = "product:" + productId;
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 3. Redis缓存失败,查数据库(加互斥锁防止缓存击穿)
String lockKey = "lock:product:" + productId;
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
try {
// 双重检查
product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
product = productMapper.selectById(productId);
if (product != null) {
// 写入Redis,过期时间随机化防止缓存雪崩
redisTemplate.opsForValue().set(key, product,
30 + new Random().nextInt(10), TimeUnit.MINUTES);
} else {
// 缓存空值防止缓存穿透
redisTemplate.opsForValue().set(key, new NullProduct(),
5, TimeUnit.MINUTES);
}
return product;
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 未获取到锁,等待500ms后重试
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return getProduct(productId);
}
}
// 从数据库加载商品(供Caffeine使用)
private Product loadProductFromDb(Long productId) {
String key = "product:" + productId;
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
product = productMapper.selectById(productId);
if (product != null) {
redisTemplate.opsForValue().set(key, product,
30 + new Random().nextInt(10), TimeUnit.MINUTES);
}
return product;
}
}3. 异步处理优化(CompletableFuture)
技术亮点:使用线程池隔离不同业务;支持异步回调和异常处理;提高系统吞吐量
@Service
public class OrderService {
// 自定义线程池:核心线程数=CPU核心数,最大线程数=2*CPU核心数
private final ExecutorService orderExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 2,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadFactoryBuilder()
.setNameFormat("order-thread-%d")
.setDaemon(true)
.build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用者线程执行
);
@Autowired
private SmsService smsService;
@Autowired
private LogService logService;
// 创建订单
public Order createOrder(OrderCreateDTO dto) {
// 1. 同步执行核心逻辑:创建订单
Order order = orderMapper.insert(dto);
// 2. 异步执行非核心逻辑
CompletableFuture.runAsync(() -> {
// 发送订单确认短信
smsService.sendOrderConfirmSms(order.getUserId(), order.getOrderNo());
}, orderExecutor).exceptionally(e -> {
log.error("发送订单确认短信失败,orderNo:{}", order.getOrderNo(), e);
return null;
});
CompletableFuture.runAsync(() -> {
// 记录订单操作日志
logService.recordOrderLog(order.getOrderNo(), "CREATE", "订单创建成功");
}, orderExecutor).exceptionally(e -> {
log.error("记录订单日志失败,orderNo:{}", order.getOrderNo(), e);
return null;
});
return order;
}
}4. 布隆过滤器防止缓存穿透
技术亮点:基于 Redis 实现分布式布隆过滤器;支持动态扩容;误判率低
@Component
public class ProductBloomFilter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String BLOOM_FILTER_KEY = "bloom:product";
private static final int EXPECTED_INSERTIONS = 1000000; // 预期插入100万条数据
private static final double FALSE_POSITIVE_RATE = 0.01; // 误判率1%
// 初始化布隆过滤器
@PostConstruct
public void init() {
// 计算布隆过滤器需要的位数和哈希函数数量
int numBits = BloomFilter.optimalNumOfBits(EXPECTED_INSERTIONS, FALSE_POSITIVE_RATE);
int numHashFunctions = BloomFilter.optimalNumOfHashFunctions(EXPECTED_INSERTIONS, numBits);
// 将参数存入Redis
redisTemplate.opsForHash().put(BLOOM_FILTER_KEY, "numBits", numBits);
redisTemplate.opsForHash().put(BLOOM_FILTER_KEY, "numHashFunctions", numHashFunctions);
// 预加载所有商品ID到布隆过滤器
List<Long> productIds = productMapper.selectAllProductIds();
for (Long productId : productIds) {
add(productId);
}
log.info("布隆过滤器初始化完成,共加载{}个商品ID", productIds.size());
}
// 添加元素到布隆过滤器
public void add(Long productId) {
int numBits = (int) redisTemplate.opsForHash().get(BLOOM_FILTER_KEY, "numBits");
int numHashFunctions = (int) redisTemplate.opsForHash().get(BLOOM_FILTER_KEY, "numHashFunctions");
long[] hashes = getHashes(productId.toString().getBytes(), numHashFunctions);
for (long hash : hashes) {
long index = Math.abs(hash % numBits);
redisTemplate.opsForValue().setBit(BLOOM_FILTER_KEY + ":bits", index, true);
}
}
// 判断元素是否存在
public boolean mightContain(Long productId) {
int numBits = (int) redisTemplate.opsForHash().get(BLOOM_FILTER_KEY, "numBits");
int numHashFunctions = (int) redisTemplate.opsForHash().get(BLOOM_FILTER_KEY, "numHashFunctions");
long[] hashes = getHashes(productId.toString().getBytes(), numHashFunctions);
for (long hash : hashes) {
long index = Math.abs(hash % numBits);
if (!redisTemplate.opsForValue().getBit(BLOOM_FILTER_KEY + ":bits", index)) {
return false;
}
}
return true;
}
// 计算哈希值(使用MurmurHash算法)
private long[] getHashes(byte[] bytes, int numHashFunctions) {
long[] hashes = new long[numHashFunctions];
long hash1 = MurmurHash.hash64(bytes, bytes.length, 0);
long hash2 = MurmurHash.hash64(bytes, bytes.length, hash1);
for (int i = 0; i < numHashFunctions; i++) {
hashes[i] = hash1 + i * hash2;
}
return hashes;
}
}⚠️ 技术难点与解决方案 🧩
| 技术难点 | 问题描述 | 解决方案 |
|---|---|---|
| 限流阈值动态调整 | 固定阈值无法适应流量波动;调整阈值需要重启服务 | 1. 使用配置中心(Nacos/Apollo)动态推送限流规则 2. 基于监控数据自动调整阈值(如 CPU 使用率超过 80% 时自动降低阈值) 3. 支持按用户 / IP / 接口维度精细化限流 |
| 熔断后优雅恢复 | 服务恢复后流量瞬间涌入导致再次熔断 | 1. 使用半开状态,逐步放行流量 2. 采用指数退避算法,逐步增加放行比例 3. 结合监控数据,当服务指标恢复正常后再完全放开 |
| 缓存与数据库一致性 | 缓存更新不及时导致数据不一致;双写不一致问题 | 1. 采用 "先更新数据库,再删除缓存" 的策略 2. 使用延迟双删机制,确保缓存最终一致 3. 对于强一致性要求的场景,使用分布式事务 |
| 分库分表后的事务一致性 | 跨库事务无法保证 ACID 特性 | 1. 尽量避免跨库事务,合理设计分库分表规则 2. 使用柔性事务(如 Seata TCC 模式) 3. 采用最终一致性方案,结合消息队列实现 |
| 全链路压测模拟真实流量 | 测试环境与线上环境差异大;无法模拟真实的流量特征 | 1. 使用流量录制工具(如 GoReplay)录制线上流量 2. 在测试环境回放流量,模拟真实场景 3. 逐步放大流量,测试系统极限 |
| 线上问题快速定位 | 线上问题复杂多样;日志和监控信息分散 | 1. 建立全链路追踪系统(如 SkyWalking、Zipkin) 2. 使用 Arthas 等工具进行线上实时诊断 3. 完善日志体系,统一日志格式和级别 |
| 大流量下的数据库性能 | 数据库成为系统瓶颈;连接池耗尽;锁竞争严重 | 1. 读写分离,读操作走从库 2. 分库分表,水平拆分大表 3. 优化 SQL 和索引,减少慢查询 4. 使用数据库连接池,合理设置连接数 |
💡 预防措施
- 压测常态化:定期进行全链路压测,提前发现瓶颈
- 监控告警完善:建立完善的监控告警体系,及时发现问题
- 容量规划:根据业务增长提前规划容量
- 应急预案:制定完善的应急预案,并定期演练
真实面试模拟
真实面试模拟
面试官 😊:
“好,咱们聊个实际的场景。假设你负责的一个核心线上接口,突然负载打满了,CPU 飙到 90% 以上,响应越来越慢,大量请求超时。你会怎么一步步解决这个问题?”
候选人 💪:
“谢谢面试官,这是一个典型的线上高并发故障。我一般会按这个思路来:先止损,再定位,后根治。可以先用一张图概括我的处理流程。”
“这个思路的核心就是别一上来就翻代码,先保证系统活着。”
面试官 👍:
“说得不错。那第一步应急止损,你能具体说说你实操过的手段吗?”
候选人 🎯:
“好的。应急我总结为三板斧,通常在 1 分钟内就要完成。
1.限流 🚦
- 在网关层,比如 Nginx 或 Spring Cloud Gateway,立刻配置令牌桶/漏桶算法限流。
- 比如 Nginx 里直接
limit_req_zone,把该接口压到 500 QPS,超出的直接返回 429 状态码,前端展示‘活动太火爆,请稍后再试’。 - 这样虽然部分用户被拒,但系统不会崩。
2.降级 ⬇️
- 非核心链路果断降级。比如首页的‘猜你喜欢’、‘积分查询’,我会用 Sentinel 或 Hystrix 配置 fallback,直接返回缓存里的兜底数据,甚至一个空列表。
- 这样能把宝贵资源留给下单、支付这种核心链路。
3.紧急扩容 🖥️
- 如果是 K8s 环境,直接调大目标副本数,
kubectl scale一键操作;如果是虚拟机,先加几台机器横向扩展。 - 同时观察 CPU 和 RT 是否回落。
这三步一上,基本上 1 分钟内就能把系统从濒死边缘拉回来,给后面定位争取时间。”
面试官 🤔:
“嗯,很实战。血止住以后,你怎么快速找到根因?你会先看哪些指标?”
候选人 🔍:
“我会并行看四块,每块之间的信息互相印证。
1.📈 监控大盘(Prometheus + Grafana)
立即看 QPS、RT、错误率、CPU、内存、线程池使用率。如果 QPS 没变但 RT 突增,基本是内部处理逻辑变慢;如果 QPS 突然翻了数倍,可能是刷流量或营销活动。
2.🗂️ 慢 SQL 面板(Druid 监控页 / Arthas / SkyWalking)
我经历过的大部分负载高,元凶都是某条 SQL 突然走了全表扫描。一看慢 SQL 排名,Top 1 可能就是这个接口里的查询。
3.🔗 链路追踪(Jaeger / Zipkin)
看一次请求的耗时分布:是调用下游的用户服务耗时 2 秒?还是自己序列化/反序列化把 CPU 打满了?
4.🧵 线程栈(jstack 或 Arthas 的 thread -b)
如果怀疑死锁或线程全部阻塞在某个方法,看一眼线程栈就知道线程在等什么锁。
举个例子:之前我遇到一次,监控显示 DB 连接数爆满,同时慢 SQL 里排第一的正好是商品查询接口的语句。顺着链路追踪一看,是因为缓存大面积失效,所有请求都穿到 DB,几十万条数据全表扫描,直接把接口拖垮。定位就用了不到 3 分钟。”
面试官 💡:
“这个例子很好。那找到原因之后,你如何做根治性的优化?能分场景举一些你实际用过的技术手段吗?”
候选人 📋:
“我会按照根因类型来出手。我把常见场景列成了一个表,方便记忆:
| 常见根因 | 优化手段 | 备注 |
|---|---|---|
| 慢 SQL | 加索引、覆盖索引、拆大事务、读写分离 | 用 explain 看执行计划 🔍 |
| 缓存击穿/雪崩 | 布隆过滤器、互斥锁、永不过期+异步刷新 | Redis 用 SETNX 互斥锁 |
| 接口逻辑重 | 异步化(MQ)、并行调用(CompletableFuture) | 非必须同步返回的全异步 |
| 流量突增 | 削峰填谷(MQ 队列缓冲)、CDN、页面静态化 | 秒杀场景常用 |
| 下游依赖慢 | 合理超时+熔断+隔离线程池 | Sentinel 线程池隔离 |
| GC 频繁 | 调优 GC 策略、减少对象创建、使用堆外内存 | 高并发下考虑 CMS/G1 |
我举个具体的 Java 例子吧:有个接口内部循环调用用户服务去查用户头像,当用户量到 10 万时,RT 直接从 50ms 飙到 3 秒。
我的优化是:先用 CompletableFuture 将循环调用并行化,然后推动用户服务提供批量查询接口,一次批量拿到所有头像,最后 RT 降回 80ms 左右。✈️”
面试官 🌟:
“听起来很系统。那如果让你从架构层面做长期预防,你会建设哪些能力?”
候选人 🧩:
“我会在四个方向做沉淀。
- 压测常态化 🏋️:每次大促前,用 JMeter 或 Locust 做全链路压测,提前找到瓶颈点。
- 弹性扩容策略 📐:K8s 的 HPA 基于 CPU/内存自动伸缩,还可以加预测性扩容,比如根据历史流量。
- 限流前置 🛡️:在 CDN 或网关层就挡住非法请求、爬虫,不要等流量打到后端再限流。
- 可观测性体系 👀:确保日志、指标、链路追踪齐全,目标是任何故障 5 分钟内能定位。
这样,虽然不能 100% 杜绝故障,但能让每次的故障都变成系统韧性提升的机会。”
面试官 😄:
“好,最后给你 30 秒,用一段话总结一下你的这套打法。”
候选人 ✨:
“面对线上接口负载满,我习惯先通过限流、降级、扩容快速止血,然后结合监控、慢 SQL、链路追踪精准定位根因,最后根据原因做缓存、异步、索引优化等彻底解决。同时把每次故障的经验沉淀到压测和容量规划里,避免重复踩坑。这套组合拳既有短期的应急响应,也有长期的系统韧性。”
面试官 👨💻:
“你刚才提到了不少技术细节,能挑几个核心场景,给我展示下有亮点的代码实现吗?另外,把这类问题常见的技术难点和你的解决方案总结一下。”
候选人 🧑💻:
“没问题,我拿三个最有代表性的场景,写一下核心代码,然后总结难点。”
🧬 场景一:网关层令牌桶限流(Nginx + Lua)
亮点:在流量入口毫秒级限流,避免后端过载,且动态可配。
# nginx.conf
http {
# 定义限流规则:单个 IP 每秒最多 5 个请求,突发 10 个
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;
limit_req_zone $server_name zone=perserver:10m rate=500r/s;
server {
location /api/core {
# 拒绝超限请求,返回 429
limit_req zone=perip burst=10 nodelay;
limit_req zone=perserver burst=50 nodelay;
# 自定义限流返回
error_page 503 =429 /rate_limit.html;
proxy_pass http://backend;
}
}
}技术亮点
- 使用令牌桶算法,允许一定突发流量(burst),又保证平均速率不超标。
- 双层限流:
perip防单一用户刷,perserver保整站水位。 - 限流状态直接返回 429,前端统一处理“活动太火爆”提示,减少后端的无效请求堆积。 🚦
🧬 场景二:缓存击穿——互斥锁加载
亮点:高并发下只让一个线程去加载热点数据,其余等待结果,避免数据库崩溃。
public String getHotData(String key) {
// 1. 先查缓存
String value = redis.get(key);
if (value != null) {
return value;
}
// 2. 缓存未命中,加互斥锁防击穿
String lockKey = "lock:" + key;
try {
// 设置锁,超时 10 秒防死锁
if (redis.setnx(lockKey, "1", 10, TimeUnit.SECONDS)) {
// 3. 双重检查,可能其他线程已加载
value = redis.get(key);
if (value != null) {
return value;
}
// 4. 查数据库
value = db.query(key);
// 5. 写入缓存,逻辑过期时间 + 异步刷新
redis.set(key, value, 30, TimeUnit.MINUTES);
} else {
// 6. 没拿到锁,短暂休眠后重试(或自旋)
Thread.sleep(50);
return getHotData(key); // 重试
}
} finally {
// 7. 释放锁(注意原子性,可用 Lua 脚本)
redis.del(lockKey);
}
return value;
}技术亮点
SETNX实现轻量级分布式锁,避免大量请求同时穿透到 DB。- 双重检查(DCL)确保获锁线程不会重复加载数据。
- 锁超时防死锁,释放锁用 Lua 脚本保证原子性(示例简化了)。
- 实际生产会配合逻辑过期和异步刷新,真正扛住热点 Key 瞬间压力。 🗝️
🧬 场景三:接口逻辑优化——CompletableFuture 并行调用
亮点:原来循环串行调用下游,改成并行 + 批量,RT 下降 95%。
// ❌ 优化前:串行调用,RT = N * 单次耗时
for (Long userId : userIds) {
UserProfile profile = userService.getProfile(userId);
profiles.add(profile);
}
// ✅ 优化后:使用 CompletableFuture 并行调用 + 最终批量
public List<UserProfile> getProfiles(List<Long> userIds) {
// 1. 拆分为多个异步任务
List<CompletableFuture<UserProfile>> futures = userIds.stream()
.map(id -> CompletableFuture.supplyAsync(() ->
userService.getProfile(id), executor)) // 自定义线程池
.collect(Collectors.toList());
// 2. 等待所有任务完成,并收集结果
return futures.stream()
.map(CompletableFuture::join) // 阻塞等待,但整体 RT ≈ max(单次耗时)
.collect(Collectors.toList());
}更优方案(推动下游提供批量接口):
public List<UserProfile> getProfilesBatch(List<Long> userIds) {
// 一次 RPC 批量查询,避免网络往返放大
return userService.batchGetProfiles(userIds);
}技术亮点
- 利用
CompletableFuture.supplyAsync+ 自定义线程池,将串行网络等待转为并行,RT 从 O(N) 降到 O(1)。 - 自定义线程池大小 = CPU 核数 * 2 + 网络 IO 等待比例,避免创建过多线程。
- 更好的是从根源上减少调用次数,推动下游提供批量接口,网络 IO 从 N 次降到 1 次。 🚀
🧩 本场景技术难点 & 解决方案汇总
| 技术难点 🤔 | 风险说明 ⚠️ | 解决方案 ✅ | 落地工具/代码示例 🛠️ |
|---|---|---|---|
| 高并发下热点缓存失效 | 瞬间数万请求穿透到 DB,拖垮数据库 | 互斥锁 + 逻辑过期 + 异步刷新 | Redis SETNX 分布式锁、Caffeine 本地缓存 |
| 限流粒度与用户体验平衡 | 限流太严误伤正常用户,太松系统崩溃 | 多级限流(IP+全局)+ 优雅降级提示 | Nginx limit_req、Sentinel 流控 + Fallback |
| 瞬发流量削峰 | 秒杀或大促流量瞬时飙升,超过系统承载 | 消息队列缓冲 + 前端排队机制 | RocketMQ/Kafka 异步下单,Redis 计数器排队 |
| 下游服务变慢拖累调用方 | 一个慢依赖耗尽调用方线程池,导致雪崩 | 线程池隔离 + 超时熔断 | Sentinel 线程池隔离、Hystrix fallback |
| CPU 飙升却无显式慢 SQL | 循环内大量反射/序列化/正则计算 | 火焰图定位热点方法 + 并行化/优化算法 | Arthas profiler、CompletableFuture 优化 |
| 快速定位瓶颈 | 依赖众多,故障点难找,定位时间长 | 全链路追踪 + 统一监控大盘 + 慢SQL/GC 日志 | SkyWalking、Prometheus、Druid 监控 |
| 应急扩容不够快 | 传统应用扩容分钟级,流量已经把服务冲垮 | 容器化 + HPA + 镜像预热 + 弹性策略 | Kubernetes HPA,提前扩容预案 |
| 风险二次发生 | 修复后没有沉淀,下次活动又重现 | 全链路压测常态化 + 容量规划文档 | JMeter/Locust 压测,容量水位告警线 |
面试官 😄:
“代码写得很实战,难点总结也很清晰。看来你在高并发治理上积累了不少一线经验,这个场景题你通过了。”
候选人 🙏:
“谢谢面试官,其实都是踩坑踩出来的,每次故障后复盘沉淀,就慢慢形成了一套肌肉记忆。”
