高并发40问:从QPS到数据库、缓存、限流的全链路解析

发布时间:2026/10/8 15:23:51
高并发40问:从QPS到数据库、缓存、限流的全链路解析
这段时间又把高并发相关的40个高频问题重新梳理了一遍起因是团队在压测IM网关时发现很多平时觉得“懂”的概念真到定位瓶颈时全对不上。比如连接数和QPS什么关系、为什么数据库最先挂的往往是连接而不是SQL、群聊消息为什么不能一条条落库。这份学习笔记与其说是面试题集不如说是一张排查线上问题的索引图。它适合两类人看一类是准备高级开发或架构师面试的人另一类是正打算做容量评估、性能压测但还没把链路串起来的同学。我按“流量从进来到离开”的顺序组织了这40问尽量把每一问的答案落到“为什么”和“现场怎么处理”上。1. 从数量和延迟聊起高并发到底在拼什么1.1 并发数、QPS、RT的真实换算关系问题1高并发是不是就是“在线用户多”不是。真正压垮系统的是单位时间内的请求量以及请求在系统里停留时占用的资源。在线用户多但大家都不动系统其实很轻松一旦集中点击请求涌进来就开始排队了。高并发本质是“请求到达速率超过系统处理速率”的状态所以别只看在线数要看QPS和并发在途请求数。问题2RT、QPS、并发数之间怎么算这里有一条支配整个高并发设计的基础公式很多人面试挂在它上面在途并发数 QPS × 平均RT秒举例一个接口QPS是1000平均响应时间50ms那么在途请求就是 1000 × 0.05 50。也就是说这个系统任意时刻只有50个请求在“飞”。如果RT变成100ms相同QPS下在途并发就变成100占用的线程、连接、内存全部翻倍。更麻烦的是并发上来之后CPU和锁开始竞争RT继续恶化QPS反而往下掉形成一个恶性循环。这就是为什么高并发系统最怕“响应变慢”慢比挂更致命。1.2 读多写少才是高并发的主旋律问题3为什么大多数高并发系统都在强调缓存因为绝大部分业务是读多写少。一个内容型产品读和写的比例可能超过100:1。缓存的意义不是“快”而是用极低成本的读请求把数据库流量截住。假设缓存命中率95%打到MySQL的只剩5%。如果命中率掉到80%数据库压力立刻变成原来的4倍。所以缓存命中率是比接口RT更值得盯的指标。问题4一条请求从进入到返回要经过哪些组件DNS解析之后流量一般经过四层负载均衡LVS类→七层负载均衡Nginx类→网关鉴权、限流、灰度→应用服务→本地缓存/Redis→数据库→可能还有消息队列异步落地。每一跳都是一次延迟累加也都是一个可能排队的节点。高并发设计不是让某一段特别快而是让每段的容量和它面对的流量匹配哪一段先撑不住哪里就是瓶颈。问题5怎么评估一个系统的容量上限唯一可靠的方式是压测不是估算。用小并发逐步加压观察QPS曲线和RT曲线RT在一个并发点之后突然陡增说明系统已经饱和了这个点就是单实例容量。规划集群规模时目标QPS×平均RT得到总在途并发再除以单实例能扛的在途请求数就能算出大致实例数。注意留冗余通常压测到70%就开始扩容预警。2. 入口分流与无状态设计先让流量有序进场2.1 分层的逻辑每一层只干一件事问题6高并发系统为什么要分层不分层的系统所有能力耦合在一起扩容只能整个集群一起复制成本高、问题定位难。分层之后每一层职责单一四层负载只管转发TCP流量Nginx管七层路由和静态资源网关管认证和限流应用层管业务编排存储层管数据。每一层都可以独立水平扩展也都能单独设置超时和降级策略。用生活类比机场安检不会让旅客直接冲上飞机而是排队、验证、分流。每一道关卡只解决一个问题整体吞吐才可控。问题7LVS、Nginx、网关这三个东西是不是重复了不重复各管一层。LVS工作在四层处理海量TCP连接性能极高但做不了URL级别的路由Nginx工作在七层能做路径转发、静态文件、限流但对业务协议理解有限网关Kong、APISIX、Spring Cloud Gateway一类能解析业务报文做登录态校验、灰度路由、熔断。实际踩坑经验是别把业务逻辑写进Nginx也别让网关扛静态流量。2.2 无状态和水平扩展的关系问题8为什么服务必须无状态才能加机器“无状态”的意思是任何一台机器重启或下线都不影响后续请求的完整性。最常见的反例是Session放本地内存用户登录落在A机器负载均衡把下一个请求转发到B机器B没有这台机器上的Session用户就被踢下线了。解决方案是让状态外置Session放到Redis或者直接用JWT这类自包含Token请求本身携带身份信息。无状态设计是水平扩展的前提吃透这一点很多“扩容后还是扛不住”的诡异问题都能解释通。问题9水平扩展和垂直扩展怎么选垂直扩展是换更强的机器简单但天花板很明显而且贵水平扩展是加机器理论上可以无限扩容。但水平扩展有三个隐含前提应用无状态、入口能均匀分发、下游存储数据库、缓存扛得住连接增长。很多系统加机器加到一定数量就上不去了卡在数据库连接数上这就是后面MySQL专项要解决的问题。问题10几百台机器一起跑怎么定位一个慢请求没有链路追踪分布式系统就是黑盒。至少要在入口生成一个全局Trace ID从网关日志一直透传到MySQL慢查询日志和Redis慢日志。选型上可以看OpenTelemetry、SkyWalking、Zipkin。我的建议是压测之前就接好链路追踪否则瓶颈在哪一层都不知道盲目调优等于瞎忙。3. MySQL高并发专项缓存扛不住时数据库必须硬气3.1 连接瓶颈与连接池策略问题11MySQL在高并发下最先挂的是什么大部分人的直觉是SQL慢实际上最先挂的经常是连接数。每个MySQL连接都要占用线程和内存连接太多会导致上下文切换、锁竞争和内存耗尽报错too many connections。所以高并发治理数据库的第一步永远是管好连接控制总量、缩短占用时间、避免长事务。问题12连接池到底开多大合适这是个反直觉的问题连接池不是越大越好。HikariCP这类连接池的推荐公式大约是CPU核数×2再加少量磁盘IO补偿。因为数据库真正并行计算的能力有限100个线程抢20个CPU核心大部分时间花在线程切换上吞吐反而下降。实际业务里一个应用实例连接池设20左右通常就够了系统总连接数等于实例数乘单实例连接数必须小于MySQL的max_connections否则就是互相挤兑。3.2 索引、慢查询与深翻页问题13索引失效的常见坑有哪些索引是让MySQL从“全表扫描”变成“定点查找”的关键但在高并发下常见的失效写法会让一切回到解放前对索引列用了函数、隐式类型转换、左模糊匹配、联合索引不满足最左前缀原则、OR连接非索引列。判断方法就是EXPLAIN看type字段从ALL到index到range再到ref/const性能逐级提升。线上如果出现慢查询第一件事不是加机器而是把这条SQL的EXPLAIN拿出来看。问题14深分页为什么慢怎么优化MySQL的LIMIT 100000, 20不是只读20条而是先扫描十万行再丢弃前面的数据。线上常见解法是游标分页用WHERE id last_id LIMIT 20替代页码条件里走主键索引效率高得多。还有一种延迟关联先只查主键再回表取数据减少回表开销。这个问题在列表类接口压测时几乎必现属于高并发场景下的高频坑。3.3 读写分离与分库分表问题15读写分离能扛多大流量主从延迟怎么办读写分离的本质是把读流量从主库卸走主库只处理写和少量一致性读。一个主库配多个从库读QPS能放大数倍。最常见的坑是“写完马上读”用户写了一条数据从库还没来得及同步读请求就落到从库查不到。处理方式有三种强一致场景强制走主库用Redis做短暂缓存标记把“读己之写”的请求路由到主库。同时要监控从库延迟延迟过大时要把读流量切回主库。问题16什么时候必须分库分表怎么分分库分表是高并发数据库方案里代价最大的手段原则是能不碰就不碰。触发条件通常是单表行数到千万级以上、索引膨胀导致写入变慢、磁盘和IO明显吃紧。拆分有两种维度垂直拆分是把大宽表按业务域拆成多张表水平拆分是按用户ID或订单ID取模、按范围分片。拆完才有代价跨分片事务、跨分片统计、全局唯一ID全都变复杂。ID建议用雪花算法或号段模式不要再依赖数据库自增。3.4 锁、热点行与死锁问题17库存扣减、余额更新这种热点行并发怎么做一行数据被大量并发更新时InnoDB的行锁会让所有更新串行排队吞吐上不去。解决方案分层做第一层用Redis预扣下单时原子扣减Redis库存异步落库第二层把热点行拆成多个子账户或子库存汇总查询第三层把update操作改成幂等流水比如累计已读数这种场景直接insert一条计数流水查询时SUM。无论如何SQL必须写成UPDATE ... SET stock stock - 1 WHERE stock 0不要先SELECT再UPDATE否则一定超卖。问题18间隙锁是什么死锁怎么排查InnoDB默认隔离级别是RR为了防止幻读会用间隙锁锁住一个范围。高并发插入时两个事务可能各自持有不同间隙的锁再互相等待对方间隙就死锁了。排查手段是show engine innodb status看LATEST DETECTED DEADLOCK里面会写明两个事务各持有什么锁、在等什么。治理方向多个资源按固定顺序加锁别交叉获取把大事务拆小能忍受的话把隔离级别改成RC间隙锁会少很多。问题19MySQL配置参数有哪些值得调优先级最高的是innodb_buffer_pool_size一般设为物理内存的60%到70%让数据尽量都在内存里其次是max_connections要按整个集群所有实例的连接池总和来估算再就是innodb_flush_log_at_trx_commit设为1最安全但写盘频繁可以结合业务容忍度权衡0、1、2。我见过很多人照抄网上的“优化参数清单”改完反而出问题参数必须结合压测结果调。4. IM类高并发长连接场景的玩法完全不同4.1 长连接与百万连接模型问题20IM高并发和Web高并发本质差别在哪普通Web是短连接、请求响应式用户不刷新就没有流量IM客户端和服务器之间保持长连接连接数基本等于在线用户数。也就是说百万在线就是百万TCP连接挂在服务器上。这种场景下第一瓶颈不是CPU而是文件描述符数量和每条连接占用的内存。设计目标也从“处理更多QPS”变成“稳定维持海量连接按需推送”。问题21单机怎么扛百万连接传统的BIO模型一个线程处理一个连接百万连接就要百万线程直接打爆。正确的方向是NIO多路复用比如Linux的epoll模型由少量线程管理海量连接的读写事件或者用Go这类协程友好的语言一个连接一个协程内存开销极低。系统层面要调大ulimit文件描述符上限调整TCP相关参数。实测下来单机维持几十万到上百万“挂着的连接”是可行的但真正推送消息时CPU和带宽才会被吃掉。4.2 心跳、在线状态与路由问题22长连接的保活和假连接怎么处理TCP连接在断网时不会立刻通知服务端必须靠应用层心跳。一般做法是客户端每30到60秒发一次PING服务端记录最后一次心跳时间超过阈值比如90秒就主动断开并触发下线通知。我踩过的坑是“假连接”客户端只建立了TCP连接但不再发心跳如果不做空闲清理这些僵尸连接会把内存和文件描述符慢慢吃光所以要定期扫描并踢掉心跳超时的连接。问题23消息怎么路由到正确的连接一个用户可能连在网关A另一个用户连在网关B消息发送方在A接收方在BA节点没法直接往B节点的用户连接上写数据。通用做法是把在线状态存Redis结构类似userId → 节点ID发送时先查一下接收方在哪个节点再把消息转发到对应节点由节点找到连接推送。这里有个细节Redis里的在线状态更新要及时用户断网但还没被心跳超时踢掉时会出现“路由到旧节点”一般靠重试踢下线机制兜底。4.3 群聊放大与消息风暴问题24一个万人群发消息怎么避免消息风暴群聊是IM高并发最大的放大器。一条群消息如果直接给一万个成员各推送一次就是一万次发送瞬间打爆带宽和CPU。正确思路是把“给N个人发N条”改成“一条消息投递到群再按在线成员批量扇出”同时把消息写入MQ进行削峰消费端按连接维度批量推送。离线用户不推送只在离线消息表里存一条等用户上线再拉。这背后的核心原则叫“一次写入、多次读取”。问题25消息不丢不重怎么做完全的不丢不重在分布式系统里代价极高IM通常采用“不丢尽量不重客户端去重”的折中。客户端生成唯一消息ID服务端用这个ID做去重消息带严格递增的seq序列号服务端ack超时后会重传客户端根据seq跳过重复消息。离线消息按seq分段存储用户上线后从上次拉取的seq继续拉这样重连不会丢消息。4.4 消息落库和异步持久化问题26IM的消息为什么不能同步写数据库如果一条群消息同步给一万个成员各写一条记录数据库瞬间爆炸。IM的存储设计通常是消息先进内存或Redis返回客户端成功异步批量地把消息落库按会话维度分库分表写入放在低峰期或消费批量任务里消化。把“用户看到消息”和“消息持久化”解耦是IM高并发架构的关键思路。历史消息查询可以走专门的存储组件或搜索引擎不要在在线链路上同步查库。5. 缓存与异步把压力挡在上游5.1 缓存命中率与三大故障问题27缓存真的能解决高并发读吗能但前提是命中率。Redis单实例读性能可以达到十万级QPS一台就能挡住大部分读流量。但缓存不是万能如果缓存数据频繁失效、或者业务key的分布没有热点命中率低于90%时缓存反而成了DB的一层额外跳转。所以上线缓存后要实时监控命中率低于阈值就要查查询模式和过期策略。问题28穿透、击穿、雪崩这三个坑怎么区分穿透是查一个一定不存在的key每次请求都落到数据库有人甚至会故意用不存在的ID来打库。解法是布隆过滤器拦截或者对空值也做短暂缓存。击穿是某一个热点key在过期瞬间大量请求同时打到数据库只有一个key但它是热点。解法是重建缓存时加互斥锁或者热点数据用逻辑过期、后台异步刷新。雪崩是大批key在同一时间过期或者Redis整个节点挂了流量全面贯到数据库。解法是过期时间加随机抖动、Redis部署高可用、必要时上多级缓存。这三个问题名字像成因完全不同处理方式千万别搞混。5.2 缓存一致性的现实解法问题29先更新数据库还是先删缓存这是缓存架构里最经典的争论。我的习惯是Cache Aside先更新数据库再删缓存。因为你更新缓存是个昂贵操作而且并发读可能读到旧值删缓存让下次读回源成本最低。但“先更新DB再删缓存”也有失败风险比如删缓存失败会有老数据残留上线一道补偿机制用消息队列重试或者订阅binlog异步删。网上说的“先删缓存再更新DB”在并发下容易让多个线程读旧值写缓存我踩过这个坑后再没用过这种顺序。5.3 MQ的削峰与可靠性问题30消息队列在高并发里到底扮演什么角色三个角色削峰填谷、异步解耦、流量整形。削峰最典型的是秒杀和消息推送瞬时十万请求进MQ消费端按自己能力慢慢消费数据库不会瞬间被打穿。异步解耦是把短信、通知、积分这类非核心操作从主链路里摘出去。用好MQ的关键是理解它的“缓冲”本质队列本身也会成为瓶颈入口该限流还是要限流。问题31MQ消息丢失和重复怎么办从三个环节治理发送端要服务端确认失败重试Broker要持久化落盘消费端要做幂等用消息的唯一ID去重。顺序性是个难点只有单分区内才能严格有序Kafka的话要靠相同的key路由到同一个分区RocketMQ则用同一个队列。我的经验是消息可靠性也要分级核心转账消息做到不丢不重营销推送允许少量重复别让可靠性成本拖垮整体吞吐。问题32什么样的操作才值得异步化判断标准只有一个用户是否等着这个结果。不需要立即返回的比如发通知、加积分、写日志都适合异步。需要用户感知结果的比如库存扣减、订单提交强行异步会引入“到底成没成”的确认问题反而增加复杂度。异步化不是越彻底越好链路越长排查越难。6. 限流与降级让系统活着比处理更多请求重要6.1 限流算法与限流粒度问题33限流算法怎么选四个常见算法各有适用场景固定窗口实现简单但有临界突刺问题秒杀这种瞬时报名的场景容易漏滑动窗口把时间细分流量更平滑漏桶算法强制恒定速率适合保护下游存储令牌桶允许一定突发适合正常业务波动。网关层限流一般用Redis Lua实现滑动窗口做到集群维度计数单机限流可以用令牌桶。注意限流阈值一定来自压测拍脑袋定一个数会误伤正常用户。问题34限流应该放在哪些层、按什么维度接入层按IP限流防的是恶意抓取和异常流量网关按用户ID限流防的是单个用户刷接口应用层按接口维度限流防的是热点接口把资源吃完同时还要有集群总QPS限流避免一台机器限了而整个集群没限。常见的失误是只做了单机限流导致扩容之后限流总量跟着变阈值失效。6.2 熔断、降级与超时设置问题35熔断和降级不是一回事吗很多面试者会混着答实际不一样。熔断是“发现下游持续失败主动短路”不再发请求给下游喘息时间恢复是自动触发的防御机制。降级是“主动牺牲非核心能力”比如推荐系统挂了就让接口返回缓存数据或默认列表更多时候是预案和开关人工决策。两者的共同点是保护核心链路区别在于自动还是人工、对哪个环节生效。问题36超时时间怎么设置才能不拖垮系统没有超时的远程调用就是炸弹。但超时不是越小越好太小慢请求容易被误杀太大线程和连接一直被占住。正确的做法是调用链上的超时逐层递减网关给上游服务设超时上游给下游设更小的超时下游再给第三方设更小保证最外层永远先超时并释放资源。问题37高可用兜底方案有哪些可落地的做法场景各不同但思路是给每个关键依赖准备备胎。Redis挂了降级到本地进程内缓存推荐依赖挂了返回默认排序MQ不可用降级为同步直写数据库慢一点但能保住核心链路。我的实战建议降级开关一定要能在控制台一键打开并且定期做故障演练否则真出事时没人敢按开关。7. 场景题自查把四十问拧成一条链路7.1 用秒杀题验证整条链路问题38从这40问出发秒杀系统应该怎么串起来秒杀能一次考察大部分高并发知识点如果面试或自查能把这题梳理清楚40问基本就吃透了。我的设计思路是活动前把库存预热到Redis写入脚本预估库存入口网关做用户粒度和集群粒度的双重限流令牌桶控制瞬时流量点击抢购时先用布隆过滤器拦截请求再用Redis Lua脚本原子扣减库存扣减成功才发MQ订单服务异步消费MQ落库生成订单避免数据库被打爆最后做对账把Redis库存和数据库实际库存核对。每一步都能对应到前面某个问题的解法限流对应问题33、热点行对应问题17、异步对应问题32、一致性对应问题29。7.2 用万人群聊题验证IM架构问题39一个万人群实时聊天最短的合理链路长什么样客户端连上长连接网关网关用NIO模型维持连接心跳保活问题22消息发送后先写入Redis给发送方返回成功异步把消息写入MQ消费端按群ID做扇出策略只推送在线的成员离线成员只存离线消息问题24接收方收到消息后回ack服务端根据客户端seq做去重问题25在线状态和路由关系放Redis跨节点转发查路由表问题23。历史消息走独立的异步存储链路问题26。把这五个点都答清楚IM高并发基本就过关了。7.3 自查清单与最后一问问题40为什么说高并发不是“加机器”加机器只是结果不是手段。真正决定系统能不能扛住的是每一层的容量边界是否清晰、每个依赖是否有降级预案、每条关键路径是否有数据监控。40问背后其实就三件事流量进来时如何不让它堆积分层、限流、无状态流量到了存储时如何不让它打爆缓存、异步、索引、读写分离系统异常时如何保证核心可用熔断、降级、超时。我把“40问自查单”打印成了团队压测前的checklist入口限流开了没有、每条SQL是否走了索引、连接池上限和DB max_connections是否匹配、Redis命中率目标定没定、MQ消费积压告警配没配。每次压测跑完都对一遍比临时救火管用得多。最后再分享一个切实的感受这份笔记的价值不在背下40个答案而在把答案变成你排查问题的条件反射。我见过不少同事能背出“缓存雪崩的三种解法”压测时Redis真的抖动了一下却第一反应去看数据库慢日志。学高并发最终要练的是这个——流量出了异常你能顺着链路一眼看出是哪一层在扛不住并且立刻想到该动哪里。带着这个目标去看这40问读一遍有一遍的收获。