首页 / 资讯中心 / 文章详情

高并发面试核心考点:缓存一致性、消息队列与限流熔断实战

高并发面试核心考点:缓存一致性、消息队列与限流熔断实战 ★ FEATURED ARTICLE
说句实话做后端这几年我面过的人和被面过的次数都不少。高并发这个词基本是绕不过去的一道坎跟它相关的面试题早就从“什么是缓存穿透”升级到了“你负责的系统在峰值扛过多少QPS挂了怎么恢复”这种程度。最近我在复盘自己的技术梳理笔记也顺手整理了一批面试中反复出现的追问点打算分成几期聊透。这期算是我自己整理的“高并发面试”系列第4篇重点聊聊那些看起来简单、实际上一追问就露馅的核心知识点缓存一致性、消息队列的可靠性、限流熔断降级、分布式锁与事务以及压测和调优的真实操作路径。这些东西不光是面试要答得上来更是线上系统真正跑得稳的关键。无论你是准备跳槽的候选人还是带团队的技术负责人这篇内容都值得收藏下来当检查清单用。1. 高并发场景的主线认知别只顾背组件先把链路画出来1.1 面试官真正想看的是全局视角很多时候候选人能把“Redis为什么快”“Kafka怎么保证高吞吐”背得滚瓜烂熟但只要追问一句“你系统里这条链路从用户请求到数据库中间经过哪几层每层的容量大概是多少”就立刻卡壳。高并发题目的核心不是考单个中间件而是考你在流量冲击下对整个系统的掌控力。我建议面试前先在纸上完整画一条请求链路DNS解析、CDN回源、负载均衡、网关、应用服务、缓存、消息队列、数据库、对象存储。每画一层就同步想一想这一层的瓶颈指标是什么是连接数、带宽、CPU、IO还是内存这一层的容错手段是什么是重试、降级、限流还是故障转移。能把这套链路讲清楚的人基本已经赢了八成候选人。在课堂上带新人时我也经常用一句话概括高并发系统设计本质上就三件事——分流、削峰、兜底。分流靠多级缓存和负载均衡削峰靠异步化和消息队列兜底靠限流熔断降级。任何一道面试题最后几乎都能被归到这三类里。1.2 从单点优化到分而治之的思维切换举个最常见的例子假设商品详情页的QPS从100涨到10000你会怎么做很多人第一反应是给数据库加索引、优化SQL。但其实第一步应该是“能不能不查数据库”。把商品详情拆成基础信息、库存、评价、推荐四块分别放到CDN、本地缓存、Redis集群和搜索引擎里数据库只在缓存未命中时才被访问。这本质上是把一个高压力单点拆成了多个低压力节点。同样的思路在面试里会换成各种问法比如“一个接口突然响应变慢怎么排查”比如“秒杀系统怎么设计”。准备工作不能停留在背答案要把“分而治之”这个思路练成直觉。我自己在面试候选人时最关注的不是他是否答出了某个具体方案而是他能不能在信息不完整的情况下给出合理的拆解路径。提示拿到任何高并发题目先别急着答具体方案。先在脑子里花30秒画链路图再说“我分几个层面来处理”这个习惯能让你避免一上来就往细节里钻、结果漏掉关键点的尴尬。2. 缓存面试命中率最高的三个追问点2.1 缓存一致性先分清删缓存还是写缓存缓存一致性是面试重灾区也是生产环境里最容易出事的点。很多候选人都在背“先更新数据库再删缓存”但被问到“为什么不能先删缓存再更新数据库”“为什么不能直接更新缓存”时就说不清了。先说结论业界最常用的模式是Cache Aside Pattern读的时候先读缓存读不到就读数据库然后把数据回填到缓存写的时候先更新数据库然后删除缓存。这里的关键是“更新数据库后要删缓存而不是写缓存”。为什么因为并发写场景下如果两个请求同时更新数据库后写数据库的请求可能先写缓存导致缓存里存了旧数据数据就永久不一致了。而删除缓存虽然会导致下一次读请求穿透到数据库但至少下一次读会把新数据回填最终能达到一致。但在高并发场景下简单的“先更新DB再删缓存”也存在一个时间窗口在数据库更新完成到缓存删除之间如果有读请求命中了旧缓存就会读到旧数据。解决方案也很成熟就是延时双删——先删缓存更新数据库隔几百毫秒再删一次缓存。延时的目的是让并发读请求有机会把旧数据回填到缓存第二次删除把这些“脏缓存”清掉。public void updateProduct(Product product) { // 第一次删除缓存 redisTemplate.delete(product: product.getId()); // 更新数据库 productMapper.updateById(product); // 延时第二次删除避免并发读回填旧数据 executor.schedule(() - redisTemplate.delete(product: product.getId()), 500, TimeUnit.MILLISECONDS); }注意延时双删里的延时时长不是随便拍的要大于一次读请求“读DB 回填缓存”的最慢耗时。我之前在某电商系统里把这个值设成1秒压测后发现极端情况下读请求回填要800毫秒延时太短会白删。不过说实话延时双删也只是缓解不是根治。如果想在面试里答出更高水平可以补充一个更可靠的最终一致性方案通过订阅数据库的binlog比如用Canal异步解析出变更事件再刷新缓存。这种方案的核心逻辑是“数据库为主缓存跟随”不再依赖业务代码里手动删缓存天然能规避时序问题。2.2 雪崩、击穿、穿透三种故障要分清楚这三个词被讲烂了但每次面试还是能筛掉一批人。核心原因在于很多人只记住了现象没有从“为什么发生”和“怎么预防”两个角度去拆。缓存穿透查询一个根本不存在的数据缓存和数据库都没有每次请求都直接打到数据库。如果有人恶意用不存在的ID刷接口数据库瞬间就废了。有两个经典解法一是布隆过滤器把所有存在的ID提前过滤一遍不存在的请求直接拦截二是空值缓存即便查不到数据也把一个短TTL的空值写进Redis比如60秒挡住后续相同请求。缓存击穿某一个热点key在过期瞬间大量请求同时穿透到数据库。这个跟穿透的区别是key是真实存在的只是刚好在那一刻过期了。解法是互斥锁当缓存失效时让同一个key的请求只有一个能去查数据库其他请求先等待或返回降级数据。这里有个细节锁的粒度最好是“按key维度”加锁而不是全局加锁否则所有key都会互相阻塞。缓存雪崩大量key在同一时间段集中过期或者Redis节点整体宕机导致所有请求全部打到数据库。预防措施有三个过期时间加随机值避免同一秒集体失效使用多级缓存Redis挂了还有本地缓存扛一阵Redis本身要部署高可用集群不能是单点。// 伪代码缓存击穿中的互斥锁实现 public String getProductInfo(String productId) { String cacheKey product: productId; String value redis.get(cacheKey); if (value ! null) { return value; } // 尝试获取分布式锁锁的key按商品维度区分 String lockKey lock:product: productId; boolean locked redis.setIfAbsent(lockKey, 1, 3000, TimeUnit.MILLISECONDS); if (locked) { try { value loadFromDb(productId); redis.set(cacheKey, value, 3600, TimeUnit.MILLISECONDS); } finally { redis.delete(lockKey); } return value; } // 没抢到锁的请求先返回旧缓存或降级数据 return getStaleValueFromLocalCache(productId); }实际面试中如果能把三种故障放在同一个场景里对比着讲比如“双11大促那天的商品详情页同时存在这三种风险你是如何分别应对的”会明显比干巴巴背定义有说服力。面试官要听到的是你有预案意识而不是只会写工具类。2.3 冷热数据分离与缓存容量规划除了应对故障高并发场景下缓存设计还有一个绕不开的问题哪些数据该进缓存缓存容量到底多大。我在复盘时发现不少候选人根本没算过缓存容量开口就是“把全部数据放到Redis里”。现实是Redis再快也是内存内存是有成本的全量缓存既不经济也没必要。做法是按数据的读写比和访问热度来分层。比如某商品详情页SKU数量可能有几百万条但真正会被高频访问的可能只有几千个热点商品。那就把热点商品常驻Redis非热点商品走本地缓存或者直接查库。判断热点可以用访问计数、消息队列里的访问日志分析或者直接用Redis的近似LRU淘汰策略兜底。容量规划有个粗略公式可以记单个key平均大小乘以预估的key数量再乘以1.5到2的冗余系数给扩容留余地。另外要给Redis设置合理的maxmemory和淘汰策略。我之前在某项目里配置的是allkeys-lru配合监控大盘及时发现内存增长异常。实操心得缓存命中率是个很容易忽略但很关键的监控指标。我在项目里专门做了一张报表按分钟统计各业务线的Redis命中率。正常情况下商品详情页命中率在90%以上一旦低于80%就要去查是不是缓存过期时间设置不合理或者有新版本上线导致key结构变化。3. 消息队列与异步化可靠性和顺序性是分水岭3.1 消息可靠性三个环节环环相扣异步化是应对高并发冲击的核心手段之一但“把消息发出去”谁都会真正见功力的是消息不丢、不重、不乱。面试官题目一般会从一个具体的场景切入订单创建成功后要发一条消息通知下游服务更新库存和积分。如果消息丢了怎么办消息从生产到消费完整链路是三段生产端发送、Broker存储、消费端处理。每一段都要有对应的可靠性保障。生产端使用confirm机制发送消息后等待Broker返回ack。如果ack超时或者返回失败生产端要重试发送。这里有个容易踩的坑重试不能无限重发要在发消息时给消息加全局唯一IDBroker端按ID去重。Broker端消息写入后要持久化到磁盘同时配合刷盘策略。比如RocketMQ的同步刷盘比异步刷盘慢但可靠性更高。Kafka可以通过设置acksall让消息在所有副本都写入成功后才返回ack。消费端关闭自动提交offset改为手动ack。处理完业务逻辑后再提交offset否则消息一旦没处理完就提交了宕机后就会丢消息。同时还要配置重试机制和死信队列兜住“怎么都处理失败”的消息。// 消费端手动ack示例 KafkaListener(topics order-topic) public void onMessage(ConsumerRecordString, String record, Acknowledgment ack) { try { processOrder(record.value()); ack.acknowledge(); } catch (Exception e) { // 记录失败消息到死信topic并告警 sendToDlq(record.value()); // 手动ack避免offset阻塞导致消费停滞 ack.acknowledge(); } }提示面试时讲到消息可靠性一定要主动区分“at-most-once”“at-least-once”“exactly-once”三档语义。绝大多数业务系统用的是at-least-once也就是允许重复消费但业务端必须做幂等。能主动说出这个层次的人在面试官眼里至少有架构意识。3.2 顺序消息的工程真相消息乱序是一个高频追问点。简单问题会问“Kafka如何保证消息顺序”复杂一点会问“订单状态流转的消息乱序了怎么办”。先说基础Kafka只能保证分区内的消息顺序全局顺序做不到也不建议做因为那会把并行度降到1完全违背了消息队列的初衷。所以真实场景中我们通常保证局部顺序。比如同一个订单的创建、支付、完成三条消息必须按顺序处理。实现方式是利用key进行分区让同一个订单ID的消息总是进入同一个分区。Kafka生产者里指定key就可以RocketMQ则可以通过选择消息队列来实现。但局部顺序还有一个隐藏问题消费端如果用多线程并发消费同一个分区的消息顺序还是会乱。所以要么单线程消费分区要么在消费端对消息按业务ID做内存队列分组再通过线程池处理。我见过不少团队在这里翻车——生产端分区分得好好的消费端却用了全并发顺序全乱套了。写到这里顺便提醒一句面试里如果被问到顺序消息不要只说“按key分区”就停了一定要补上“消费端也要保证单分区内串行”这个细节。这个回答一出就能显示出你真正处理过线上问题。3.3 消费幂等重复消息不是Bug是必然只要用了at-least-once语义就一定会有重复消息。想明白这件事就不会在面试里说出“我们保证不重复”这种外行话。消费幂等的核心目标是同样的消息处理两次和只处理一次业务最终状态完全一致。最基础的三种方案按成本和适用范围排序第一种唯一键约束。在数据库表中对业务唯一标识加唯一索引消费时先插入冲突则说明已处理过直接跳过。适合下游是关系型数据库的场景。第二种业务状态机校验。比如订单消息里带着“当前状态”和“目标状态”只有符合预期状态流转时才能执行更新。如果消息重复了第二次执行时状态已经变了直接丢弃。第三种去重表加事务。用一张专门的消费记录表在处理业务和写入消费记录时使用同一个本地事务保证原子性。这是经典做法但会多一次DB写操作适合对准确性要求极高的场景。-- 幂等消费示例利用唯一键去重 INSERT INTO t_order_consume_log (order_id, consume_time) VALUES (?, ?); -- 如果主键冲突说明这条消息已经消费过直接跳过业务处理实操心得幂等方案一定要在项目初期就定好基线因为一旦线上流量起来补幂等的成本会成倍上涨。我在某系统里就是在上线后才发现下游接口偶发超时导致重试消费端重复扣了库存靠手工修复数据才补回来的。那之后我们立了个规矩任何消费逻辑上线前必须写清幂等策略。4. 限流、熔断与降级保护系统不被冲垮的三板斧4.1 限流算法别再只会背“令牌桶”限流是面试里的常见题但很多人只知道“令牌桶”三个字。要答出区分度需要把几种算法放在一起对比着说清楚。固定窗口最简单把时间切分成固定长度窗口每个窗口内限制请求数。问题是临界突变比如限制每分钟100请求在第59秒第59分都被打满第60秒跨到下一个窗口瞬间可能涌入100个请求系统依然会爆。滑动窗口是对固定窗口的改进把窗口继续细分每秒一个子计数器滚动统计最近一分钟的请求数能避免临界突变问题但实现更复杂一点。漏桶算法是恒定速率不管请求多猛处理速度始终固定。适合保护下游系统但缺点是一旦流量突增大量请求会被直接丢弃或排队用户体验差。令牌桶算法允许一定程度的突发桶里积攒了令牌遇到流量峰值时可以一次性消耗大量令牌冲出去。所以令牌桶最适合“允许短时间爆发、但长期速率受限”的业务场景比如秒杀开始时瞬间涌入的流量。算法核心思想突发能力缺点固定窗口时间窗计数无临界突变滑动窗口细分子窗口滚动计数无实现复杂内存开销大漏桶恒速流出无突发流量被丢弃不够灵活令牌桶按速率补充令牌有桶大小需要合理设置工程实现上高并发服务大多基于RedisLua脚本做分布式限流保证判断和扣减两个动作是原子的。下面是一个令牌桶的简化实现思路用Redis的key存当前令牌数和上次补充时间每次请求到来时按时间差补充令牌然后扣减1个。-- 简化版令牌桶限流脚本 local key KEYS[1] local rate tonumber(ARGV[1]) -- 每秒补充令牌数 local capacity tonumber(ARGV[2]) -- 桶容量 local now tonumber(ARGV[3]) local requested tonumber(ARGV[4]) local ttl math.floor(now / 1000) 60 local data redis.call(HMGET, key, tokens, timestamp) local tokens tonumber(data[1]) or capacity local lastRefill tonumber(data[2]) or now local delta math.max(0, now - lastRefill) tokens math.min(capacity, tokens delta * rate / 1000) local allowed 0 if tokens requested then tokens tokens - requested allowed 1 end redis.call(HMSET, key, tokens, tokens, timestamp, now) redis.call(EXPIRE, key, ttl) return allowed注意单机限流可以用Guava RateLimiter或Resilience4j成本低又方便。但到了多实例部署时单机限流会出问题——比如总QPS限制1000部署了4个实例如果每个实例各限1000总流量就变成4000了。所以上规模以后分布式限流是必须的要么用Redis要么在网关层统一做。4.2 真实业务中的双层限流设计很多团队把限流只做在网关层这是不够的。我遇到过一个故障某服务接口突发流量网关的限流阈值配得偏宽流量虽然被打到了应用层但应用层没有做任何自我保护结果数据库连接池被耗尽整个服务都拖垮了。正确的做法是双层限流网关层按客户端IP或用户维度粗粒度限流拦截明显的外层攻击和流量洪峰应用层再按业务接口维度精粒度限流保护数据库和下游依赖。比如下单接口的业务阈值是1000 QPS网关层可以设1500 QPS做第一道闸应用层在本地用Sentinel或Resilience4j设1000 QPS做第二道闸。这里的关键是阈值怎么定。不能拍脑袋要结合压测数据、机器规格、上下游容量来算。比如某接口单机压测能扛800 QPS部署3台机器预留30%缓冲那么集群阈值可以设在1600左右。限流值定得太高等于没限定得太低又会误伤正常流量需要反复调优。除了两层限流还要给限流加“动态开关”。我习惯把限流阈值配置到配置中心线上需要调整时直接改配置不用发版重启。这样即使阈值设错了也能在几分钟内修正不至于因为限流配置问题而扩大故障。4.3 熔断与降级慢接口比挂了更可怕限流解决的是“请求太多”熔断和降级解决的是“依赖不可用或变慢”。面试里一个典型场景 A服务调用B服务B服务响应时间从50ms涨到5sA服务的Tomcat线程池很快就被占满最后A服务也跟着挂。这就是级联故障。熔断的核心思路是快速失败。当依赖的错误率或慢调用比例超过阈值时直接熔断后续请求不再调用下游而是返回降级结果。一段时间后进入半开状态放少量请求去试探下游是否恢复如果恢复了就关闭熔断否则继续熔断。具体参数上我常用的配置是滑动窗口10秒最小请求数20错误比例阈值50%熔断超时30秒。这套参数不是普适的要根据依赖的重要程度和失败容忍度调整。降级则是主动的行为。比如大促期间把商品详情页中非核心的“猜你喜欢”推荐模块直接降级成固定占位内容释放后端算力给核心交易链。降级的关键是先梳理业务优先级哪些功能是保命必须护住的哪些功能是可以有损关闭的。没有明确的优先级清单真到线上故障时只能手忙脚乱。实操心得熔断和降级最容易被忽视的是“对下游超时时间也要设置”。很多系统默认的HTTP连接超时是好几秒甚至不超时这在依赖故障时是非常致命的。我一般会给每一次外部调用都配置连接超时和读取超时连接超时500ms读取超时1s。宁愿主动失败也不要让线程池被慢调用拖死。5. 分布式锁、幂等性与分布式事务数据一致性的硬骨头5.1 Redis分布式锁的原子性与续期问题分布式锁是面试必考题也是线上事故高发区。经典实现是用Redis的SETNX加锁DEL解锁但很多人在“锁过期”和“业务没执行完”之间栽过跟头。如果业务执行时间超过锁的过期时间锁自动释放另一个线程拿到锁进来处理相同逻辑就会产生并发冲突。成熟的解法是“看门狗”自动续期机制比如Redisson。获取锁成功后默认启动一个定时任务每隔锁有效期的三分之一时间检查一次如果业务还在执行就把锁的过期时间续期。如果服务宕机看门狗也停了锁到点自动释放不会死锁。另外锁的value必须是一个唯一标识比如UUID。解锁时要先校验value是不是自己的再删除避免误删了别人的锁。最安全的方式是通过Lua脚本原子地执行“查value、比对、删除”三个动作。// Redisson分布式锁标准用法 RLock lock redissonClient.getLock(inventory: productId); try { // 尝试加锁最多等待5秒自动释放30秒看门狗会续期 boolean locked lock.tryLock(5, 30, TimeUnit.SECONDS); if (!locked) { throw new RuntimeException(获取库存锁失败); } doDeductInventory(productId); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }除了Redis锁面试还可能会问ZooKeeper锁。两者区别在于Redis锁是性能优先ZooKeeper锁是强一致性优先。ZooKeeper通过临时顺序节点实现客户端断开后节点自动消失不会出现锁永久不释放的问题但性能比Redis差不少。选择哪个没有绝对答案取决于业务对一致性强度和吞吐的要求。5.2 分布式事务别一上来就2PC先想清楚最终一致性高并发场景下分布式事务是让很多人发怵的考点。如果面试官问“下单服务和库存服务之间如何保证事务一致”千万别直接回答“用2PC”。以我实际经验两阶段提交在跨服务调用中的成功率并不理想而且会长期持有数据库资源高并发下根本扛不住。更务实的思路是最终一致性。经典方案是本地消息表做法是在本地事务里同时写业务表和消息表然后异步把消息表里的记录发送到MQ消费方接收到消息后执行业务并回调确认。发起方定期扫描未确认的消息重新投递。这个方案的核心价值是业务操作和消息写入在同一事务里保证“要么都成功要么都失败”不会出现业务提交了消息没发出去的情况。-- 本地消息表示例订单创建时同步写入消息 BEGIN; INSERT INTO t_order (order_id, user_id, amount) VALUES (?, ?, ?); INSERT INTO t_order_message (msg_id, order_id, status, create_time) VALUES (?, ?, 0, NOW()); COMMIT; -- 异步任务扫描status0的消息发送到MQ收到ack后更新status1对比一下其他方案消息事务比如RocketMQ事务消息本质是本地消息表的MQ内置版TCCTry-Confirm-Cancel适用于需要强实时性的场景但实现复杂度高要写大量的补偿逻辑而且要求业务能拆成可预留、可确认、可取消三个步骤。面试时能把这几个方案按“一致性强度、实现成本、吞吐影响”三个维度做对比会显得很有层次。提示讲分布式事务时千万不要只说方案名。面试官往下一追问“这个方案里消息没发出去怎么办”“对方处理失败怎么办”这些细节才是拉开差距的地方。本地消息表要答出定时对账和补偿机制事务消息要答出回查机制TCC要答出空回滚和悬挂问题。6. 压测、瓶颈分析与性能调优让面试答案落地6.1 一次有效压测的三步走高并发面试题最后通常会落到一个开放问题“你负责的接口上线前怎么评估容量”这种题没有固定答案但需要有完整的方法论。我建议按三步走第一步定指标。不要只说“支撑10000 QPS”而要同时定义延迟和成功率。一般我会用三个核心指标吞吐量QPS/TPS、平均延迟和P99延迟、错误率。比如“峰值5000 QPS平均延迟200ms以内P99延迟500ms以内错误率小于0.1%”。第二步搭建压测环境。最高保真是全链路压测连数据库专库、缓存集群都隔离一套。如果成本受限至少要做到应用日志和核心监控不干扰测试。压测工具我常用JMeter和wrk配置好线程数、压测时长、请求体后先小流量预热一遍再逐步加压。第三步设计场景。不能只压单接口要设计混合场景核心接口占60%流量非核心接口占30%异常请求占10%这样才能还原真实的流量特征。压测过程中要同步监控应用实例的CPU、内存、GC、连接池水位和下游依赖的时延。压测阶段压测目标观察指标预热让JIT和缓存达到稳定状态错误率归零基线压测摸清单机容量单机QPS、平均延迟混合场景压测验证整体容量和短板P99延迟、资源饱和度极限压测找到系统崩溃点故障时的指标表现6.2 从压测结果定位瓶颈的实战案例某次压测一个订单查询接口目标4000 QPS。压到2000 QPS时P99延迟就开始抖到2500 QPS时直接飙到3秒错误率上升。我当时的排查路径很典型也建议你在面试里按这个思路讲述。第一步看应用层线程池。发现Tomcat线程池达到最大线程数线程大量处于等待状态。这说明不是线程本身慢而是后面的环节卡住了。第二步看数据库发现连接池活跃连接数打满同时DB的CPU接近100%慢查询日志里出现了一个之前优化过的SQL。奇怪的是那个SQL加了索引怎么还会慢。第三步看执行计划才发现条件字段的隐式类型转换导致索引失效全表扫描。纠正类型后同样的SQL耗时从1.2秒降到30ms。这个案例说明压测不是跑完看个数字就完事而是要走“压测-观察-定位-优化-再压测”的循环。面试官感兴趣的不是“通过压测找到问题”这么一句结论而是你每一步的决策依据和工具手段。优化完之后还要注意一个问题解决了数据库瓶颈线程池和连接池的等待状态会缓解但QPS再往上可能又会出现新的瓶颈比如Redis带宽、GC压力、网络IO。所以性能调优是个非线性的过程每解开一个瓶颈就要重新跑一轮压测确认整条链路。实操心得调优要有“效果可量化”的习惯。每次改动上线前记录改动前后的对比数据比如“查询耗时从1200ms下降到80ms”。这些数据在面试里是极具说服力的素材远比“我优化了慢查询”这种空话值钱。7. 高频追问速查面试中容易翻车的几个点7.1 五个典型追问及回答思路根据我这些年面试和被面试的经验整理了五个最容易让候选人卡壳的追问点每个都标注了关键回避点。追问一你这套缓存方案怎么保证数据库和缓存最终一致别答“删完缓存就行了”。正确思路是结合binlog异步刷新或延时双删同时说明存在的时间窗口以及你能接受的最终一致时间。追问二消息队列怎么处理消息积压别只说“加消费者机器”。核心思路是先定位积压原因如果是消费能力不足可以临时扩容消费者并增加分区如果只是单条消息卡死导致阻塞先把阻塞消息移到死信队列恢复消费速度。生产端也要限流防止积压期间大量重试加剧问题。追问三限流误伤正常用户怎么办别答“调高阈值”。正确回答是设置用户维度配额、增加排队等待机制、对VIP用户或核心渠道设置白名单。高并发系统需要的是“不同用户不同策略”而不是一刀切。追问四分布式锁如果Redis节点宕机了怎么办别把Redisson的看门狗当成万能钥匙。要考虑持久化和故障转移同时思考是否可以接受“锁丢失”后用幂等操作兜底。具备这种补位思维会比死磕锁方案更得体。追问五你们线上系统遇到过哪些高并发问题别因为怕露怯就说“没有”。哪怕经验不足也可以说“我做过压测模拟发现过XX问题当时按XX思路解决”。面试官最怕的是候选人吹牛最不怕的是候选人主动复盘自己的弱点和学习路径。7.2 我给准备面试的朋友们的几点建议最后给准备高并发面试的朋友一些实际建议。这些也是我自己带团队时经常传达的方法论。第一把追问当成设计题来答。不要背标准答案而是拿到问题后先拆出约束条件这个场景的并发量级是多少可用性要求是什么数据一致性是多强的需求。方案没有绝对的对错只有合不合适的取舍。第二储备至少一到两个真实故障事故事例。比如“缓存雪崩引发数据库熔断”“消息积压导致下游资源耗尽”。哪怕故事是你听来的只要能讲清楚起因、处理过程、复盘改进就能证明你有架构层面的思考力。第三主动暴露权衡过程。讲方案时不要只讲优点要把“为什么不用另一个方案”也讲出来。比如不用Redis而用本地缓存是因为一致性要求高但数据量小不用Kafka而用RocketMQ是因为需要事务消息。这种权衡表达比罗列知识点更能打动面试官。第四动手做一两个高并发模拟项目。不用多复杂哪怕只是写一个带Redis缓存、MQ异步、限流降级的秒杀Demo也比你背十篇博客有用。我面过的候选人里凡是能就自己做的东西聊出细节的基本都过了。第五别忽略基础成本。高并发题的深层考察往往落在JVM内存模型、数据库索引原理、TCP连接管理这些地基上。框架和组件可以靠经验积累但基础不牢深度问题一问就穿。我个人的体会是高并发面试从来不是考记忆力而是考你在混乱和不确定中做出合理决策的能力。把这个底层逻辑想清楚你就不会再去背题而是真正开始设计系统了。
阅读完成 · 觉得有帮助?
咨询建站