线上接口负载满了如何解决

线上接口负载满了如何解决

📋 整体处理流程图

🚨 第一阶段:紧急止损(5 分钟内必须完成)

这是最关键的一步,先保住系统不雪崩,再谈其他。

  1. 接口限流:立即在网关层开启限流保护
  • 采用令牌桶算法(支持突发流量)
  • 先设置一个保守的阈值(如平时峰值的 80%)
  • 优先保障核心接口,非核心接口直接限流
  1. 服务降级:关闭非核心功能
  • 关闭推荐、评论、个性化等非核心功能
  • 返回兜底数据(如静态页面、缓存数据)
  • 关闭写操作,只保留读操作
  1. 熔断隔离:隔离故障服务
  • 对超时率高、错误率高的服务进行熔断
  • 使用线程池隔离不同业务模块
  • 防止一个服务拖垮整个集群
  1. 快速扩容:临时增加服务实例
  • 利用云平台的弹性伸缩能力快速扩容
  • 优先扩容无状态的应用服务
  • 数据库和缓存谨慎扩容(避免数据不一致)
  1. 流量切分:将流量引导到备用集群
  • 如果有异地多活架构,切一部分流量到备用机房
  • 关闭灰度发布,将所有流量切回稳定版本

🔍 第二阶段:问题定位(15-30 分钟)

止损后,立即开始定位问题根源,为后续优化提供依据。

排查维度 关键指标 常见问题
应用层 CPU、内存、GC、线程数 死循环、内存泄漏、频繁 Full GC、线程阻塞
网络层 带宽、连接数、响应时间 网络拥塞、TCP 连接耗尽、慢查询
数据层 QPS、连接数、慢 SQL 索引失效、锁竞争、连接池耗尽
缓存层 命中率、内存使用率 缓存穿透、缓存击穿、缓存雪崩
  • 监控大盘:先看整体指标,再逐步缩小范围
  • 日志分析:重点查看错误日志、慢请求日志
  • 压测复现:在测试环境模拟线上流量,复现问题
  • 工具辅助:使用 Arthas、JProfiler 等工具进行线上诊断

🚀 第三阶段:长期根治(后续优化)

找到问题根源后,进行针对性的优化,从根本上解决负载问题。

1. 代码级优化

  • 优化慢方法:将耗时操作异步化、并行化
  • 减少循环嵌套:降低时间复杂度
  • 避免重复计算:使用局部变量缓存结果
  • 优化对象创建:减少不必要的对象创建和销毁

2. 架构级优化

  • 服务拆分:将大服务拆分为多个小服务,按业务维度隔离
  • 读写分离:读操作走从库,写操作走主库
  • 异步化:将非实时操作(如发送短信、记录日志)异步处理
  • 消息队列:削峰填谷,解耦系统

3. 数据层优化

  • 索引优化:为常用查询添加合适的索引
  • SQL 优化:避免 SELECT *、避免大表 JOIN、使用分页查询
  • 分库分表:对大表进行水平或垂直拆分
  • 连接池优化:合理设置连接池大小,避免连接池耗尽

4. 缓存层优化

  • 多级缓存:本地缓存 + 分布式缓存结合
  • 缓存预热:提前将热点数据加载到缓存
  • 缓存降级:缓存失效时返回兜底数据
  • 防止缓存穿透 / 击穿 / 雪崩:使用布隆过滤器、互斥锁、过期时间随机化

💻 核心代码与技术亮点 ✨

1. 网关层限流(Sentinel 动态限流)

技术亮点:支持动态调整阈值,无需重启服务;基于 QPS 和线程数双重限流

java 复制代码
@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;支持自动过期和刷新;解决缓存击穿问题

java 复制代码
@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)

技术亮点:使用线程池隔离不同业务;支持异步回调和异常处理;提高系统吞吐量

java 复制代码
@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 实现分布式布隆过滤器;支持动态扩容;误判率低

java 复制代码
@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 复制代码
# 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,前端统一处理"活动太火爆"提示,减少后端的无效请求堆积。 🚦

🧬 场景二:缓存击穿------互斥锁加载

亮点:高并发下只让一个线程去加载热点数据,其余等待结果,避免数据库崩溃。

java 复制代码
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%。

java 复制代码
// ❌ 优化前:串行调用,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());
}

更优方案(推动下游提供批量接口):

java 复制代码
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 profilerCompletableFuture 优化
快速定位瓶颈 依赖众多,故障点难找,定位时间长 全链路追踪 + 统一监控大盘 + 慢SQL/GC 日志 SkyWalking、Prometheus、Druid 监控
应急扩容不够快 传统应用扩容分钟级,流量已经把服务冲垮 容器化 + HPA + 镜像预热 + 弹性策略 Kubernetes HPA,提前扩容预案
风险二次发生 修复后没有沉淀,下次活动又重现 全链路压测常态化 + 容量规划文档 JMeter/Locust 压测,容量水位告警线

面试官 😄:

"代码写得很实战,难点总结也很清晰。看来你在高并发治理上积累了不少一线经验,这个场景题你通过了。"

候选人 🙏:

"谢谢面试官,其实都是踩坑踩出来的,每次故障后复盘沉淀,就慢慢形成了一套肌肉记忆。"


公众号"Rain的Java大神之路"

个人博客"www.javadashen.com"