恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
高并发系统架构设计:从流量入口到数据存储的完整方案
首页
资讯中心
/
高并发系统架构设计:从流量入口到数据存储的完整方案
高并发系统架构设计:从流量入口到数据存储的完整方案
发布时间:2026/10/6 3:02:12
这道题我当面试官的时候问过很多人也帮别人模拟面试陪练过无数次。说实话十个候选人里能让我点头认可的不超过两个。不是因为大家不懂技术而是大多数人把“高并发系统架构设计”答成了技术名词堆砌——Redis、MQ、分库分表、缓存、CDN随口就来但一问到“为什么”“扛多少量”“崩了怎么办”立刻就卡壳。面试官问这个题归根结底就一件事你在高并发场景下有没有完整的、可落地的系统设计能力。这不是背几个组件就能糊弄过去的需要的是从流量入口到数据存储的整体思考是每一层选型背后的取舍逻辑是出问题之后的兜底方案。这篇文章我就把这套东西完整拆开揉碎讲清楚不管你是准备面试还是真在负责高并发系统都能拿去直接用。1. 面试官到底在问什么1.1 面试官眼中的标准答案长什么样很多人以为面试官想听一套标准架构Nginx负载均衡、Redis缓存、RabbitMQ削峰、MySQL分库分表觉得把这些名词一字排开就算答完了。但面试官心里其实在评估三件事。第一你有没有量化思维。你说你的系统高并发到底多高是每秒100请求还是每秒10万请求不同量级下架构方案天差地别100 QPS用一台服务器加个数据库就足够了非要上一套微服务加消息队列那是过度设计。面试官想听到你说出具体的数字并且基于数字推导出方案。第二你有没有分层意识。高并发不是某一个组件能解决的而是接入层、应用层、缓存层、数据层、异步层各司其职每层解决一类问题。你上来就说“我用Redis扛”那缓存穿透了怎么办数据库写压力大了怎么办消息队列堆积了怎么办这些都不是单一组件能兜住的。第三你有没有工程落地能力。方案说完之后面试官一定会追问细节缓存和数据一致性怎么保证消息丢失了怎么处理分布式事务怎么做接口的幂等性如何设计你的方案如果只是PPT级别的概念一问细节就露馅。说白了面试官要的不是一个正确答案而是一整个思考过程。你如何从业务场景出发分析瓶颈权衡选型最终给出一个可实施、可运维、可扩展的方案。这跟写代码是一样的需求分析永远比编码本身更重要。1.2 从并发量到架构方案的推导链我把高并发架构设计拆成一条推导链你只要沿着这条链走逻辑就不会乱。第一环是业务场景。你得先搞清楚是什么类型的系统。是电商秒杀还是社交信息流还是支付转账不同类型的系统有不同的瓶颈。秒杀是读多写少、瞬时流量极高信息流是读多写多、关注的是个性化推荐和延迟支付系统则是数据强一致、绝对不能丢单。第二环是流量预估。面试官问“你的系统能扛多少并发”你不但要给出目标峰值还要说明依据。比如你做了促销活动预计100万用户参与每人点击5次集中在30分钟内那么平均QPS大约就是100万乘以5除以1800秒约等于2778。考虑到流量毛刺你需要预留2到3倍余量目标设计值大约就是8000到10000 QPS。第三环是分层拆解。这些QPS打进来之后每一层各自的承受能力是多少Nginx单机可以扛5万以上应用层单机Java服务大概在1000到3000 QPS视业务复杂度而定Redis单节点读QPS在10万级别MySQL单库一般建议控制在5000 QPS以内。哪一层会先成为瓶颈就在哪一层加对应的解决方案。第四环是方案选型。缓存扛读队列扛写分库扛总量限流扛毛刺。每一环的选型都要基于前面的量化和分层分析而不是凭空堆砌。这条推导链走完你在面试官眼里就是“有系统思维”的候选人而不是只会背名词的复读机。1.3 常见错误把架构设计答成技术名词堆砌我统计过候选人答题的共性错误排第一的就是在没有量化、没有场景的情况下上来就甩一堆组件名。有人一开口就是“我们用了Spring Cloud微服务、Redis集群、Kafka消息队列、ElasticSearch做搜索、分库分表中间件”听起来很唬人但面试官接下来只要问一句“你这个Kafka主要用来解决什么问题不用行不行”很多人就答不上来了。另一个很常见的错误是把“高并发”等同于“多线程”。确实Java并发编程是高并发系统里的基本功但线程池、锁、CAS这些解决的是单机内的并发资源竞争问题属于微观层面的手段。架构设计解决的是宏观层面的流量分发、容错、扩展问题。你说你用了ThreadPoolExecutor处理任务这很好但它替代不了消息队列削峰填谷也替代不了分库分表分摊存储压力。两边根本不冲突但是层次完全不同。还有一类人特别可惜技术功底不差但答得太散。他讲缓存能讲半小时问他数据库压力大怎么办他说“分库分表”。再问分了之后跨库查询怎么做他不吭声了。这就是典型的缺少完整框架抱着一个问题死磕没法把整个系统串联起来。所以你看面试官问“高并发系统架构设计”考的不是某一个技术点而是你能不能从宏观到微观、从流量到存储、从正常到异常把整个系统有条理地讲明白。下面我就按这个框架把每一层的设计思路和核心细节展开讲。2. 高并发架构的分层设计2.1 接入层流量入口的第一道闸门先讲接入层因为所有请求进来第一脚踩在这里。接入层要解决的问题不是业务逻辑而是怎么把流量安全、均匀、高效地放进来。负载均衡是接入层的基本功。DNS轮询、Nginx反向代理、硬件F5、云上的SLB本质上都是把请求分发到多台服务器上避免单点过载。Nginx作为七层负载均衡天然支持HTTP协议层面的处理比如URL路由、静态资源缓存、gzip压缩还能直接做一些简单的限流配置。配合Keepalived实现VIP漂移Nginx自身也能做到高可用。比负载均衡更关键的是限流和熔断。限流的意思是系统每秒只能处理1万请求你就别放进来2万多余的要么排队要么直接拒绝。常见的限流算法有计数器、滑动窗口、令牌桶和漏桶。令牌桶允许一定程度的突发流量适合大部分互联网场景漏桶则强制平滑流量适合对突发流量敏感的下游系统。熔断更接近一种保护机制。当下游服务出现故障、响应时间飙升时接入层应该快速切断对该服务的调用走降级逻辑而不是继续傻等把故障放大到整个调用链。Sentinel和Hystrix都是干这个事的但Sentinel对Java生态支持更好而且控制台做得完善目前用的更多。CDN也属于接入层的一部分但容易被人忽略。像图片、CSS、JS这种静态资源直接划给CDN处理就行了完全不用打到应用服务器。很多高并发系统的实际经验是静态资源占了整体流量的大头用CDN一挡应用层的压力直接少一半。接入层还有一个容易被忽略的工作叫作安全防护。不管是恶意刷接口、搞缓存穿透还是带恶意参数的请求都应该在网关层做基础的校验和拦截。黑名单、白名单、IP限流、参数校验、鉴权能前置的都前置不要等流量打到业务代码里再处理。2.2 应用层服务拆分与无状态化过了接入层请求就到了应用层。这一层是整个架构的核心计算单元也是高并发设计里最考验功力的地方。应用层第一个关键设计是无状态化。什么叫无状态就是你任意一台服务器在处理同一个请求时不需要依赖本地存储的会话数据。如果把用户登录信息存在服务器内存的Session里那用户下次请求被负载均衡分到另一台机器就找不到Session了这叫有状态它要求负载均衡做会话保持限制了系统的水平扩展能力。解决方式是把会话状态外置。登录状态放到Redis或Token里用户信息每次都去Redis取。这样所有应用实例都是平等的流量来了随便分到哪台机器都能处理想扩就扩想缩就缩这才是水平扩展的前提。面试官问“你们的服务怎么支持水平扩展”如果你回答“把Session放到Redis”这就答到点子上了。服务拆分也是应用层绕不开的话题。一个系统一开始都是单体应用所有代码凑在一起开发效率高但当流量上来之后单体服务的启动时间、发布影响面、故障隔离能力都成了瓶颈。这时候就需要按业务边界拆微服务比如订单服务、用户服务、库存服务、支付服务各拆一个模块。服务拆分有几个原则第一按业务能力划分而不是按技术层次划分第二服务之间通信优先选轻量级协议内部服务可以用gRPC这类高性能RPC框架第三每个服务有自己独立的数据库避免多个服务直连同一个库否则拆了服务不拆库等于白拆。微服务带来的新问题也不容忽视。服务调用链变长之后任何一个环节抖动都可能拖垮整个链路所以必须引入全链路追踪如SkyWalking、Zipkin和分布式日志体系。再有就是配置管理几十个服务的配置必须统一管理Nacos、Apollo这类配置中心基本是标配了。应用层内部还有一个微观层面的高并发话题就是Java多线程编程。单个应用实例的QPS上限取决于线程池配置、锁竞争强度、IO模型的综合表现。对于CPU密集型任务线程数设成CPU核心数加一就够了对于IO密集型任务线程数可以设到CPU核心数的两倍甚至更多。这些参数的设置直接影响单机性能上限也是面试时的高频追问点。2.3 缓存与数据层读多写少时怎么扛高并发系统里最宝贵的资源其实就是数据库。数据库的读写能力是有上限的再怎么优化SQL、加索引单库的支撑能力也就摆在那里。所以业界有一句老话高并发系统的核心就是尽量让请求不要打到数据库。缓存就是做这件事的。整个缓存体系从快到慢可以分为几层CPU缓存、进程内缓存、分布式缓存、数据库本身的BufferPool。在实际的架构设计里最常用的是进程内缓存加Redis分布式缓存这两层。本地缓存的优势是快零网络开销适合存放系统配置、基础字典这类变化极少的静态数据。Caffeine是Java领域本地缓存的首选性能比ConcurrentHashMap自带方案好很多。但本地缓存有个硬伤就是多实例之间数据不一致而且更新时需要主动失效所以在分布式环境下本地缓存只能存不太敏感的数据。Redis作为分布式缓存的标配能扛的读QPS单实例就在10万以上配合主从复制和哨兵模式还能提供高可用。但Redis不是万能的它只是把数据库的压力转移到了自己身上你照样得考虑缓存容量、淘汰策略、缓存一致性、缓存穿透等一系列问题。我见过很多架构Redis加了一大堆数据库照样被压垮因为缓存命中率太低了。缓存设计有一个黄金法则热点数据才值得缓存。你要是把所有数据都塞进Redis光序列化、内存占用就够你喝一壶而且缓存命中率上不去数据库该扛的压力一点没少。所以在设计缓存时第一件事是把业务里真正的高频访问数据识别出来针对性地设计缓存Key和过期策略而不是一股脑全部缓存。缓存和数据保持一致性问题我会在下一章详说这里先记住一个总原则缓存是加速手段不是数据源数据库才是最终的事实来源任何缓存数据都要能追溯到数据库且允许在一段极短的时间内存在不一致。2.4 异步化与削峰把同步压力变成异步缓冲高并发场景最典型的特点是流量具有突发性峰值可能是平均值的几十倍。如果系统完全按峰值流量设计资源浪费巨大如果按平均值设计峰值一来就全崩。异步化和削峰填谷就是解决这个矛盾的核心工具就是消息队列。消息队列的核心价值在于解耦和缓冲。当一个请求不需要立即拿到最终结果时比如用户下单后发短信、扣积分、生成对账单这些操作完全可以丢进消息队列由下游任务慢慢消费。用户端只要得到“下单成功”的响应就够了后面的流程异步去跑返回更快用户体验反而更好。常见的消息队列选型有Kafka、RocketMQ、RabbitMQ每家各有侧重。Kafka吞吐量极高适合日志收集、数据管道这类场景RocketMQ在金融和电商领域用得很多支持事务消息消息不丢失的机制做得比较完善RabbitMQ功能丰富适合业务复杂度高、对消息可靠性要求高但对吞吐要求没那么极端的场景。消息队列有一个经典组合用法前端请求写入消息队列之后立刻返回“排队中”后端消费者按照自己最大的处理能力去消费这样无论流量尖峰多大后端服务都不会被打垮。这就是秒杀系统的核心设计思路——先把流量挡在MQ这一层然后慢慢消化。但引入消息队列不等于高枕无忧你自己要面对三个新问题消息丢失、重复消费、消费积压。消息丢失要分三个层面看生产端、Broker、消费端各管一段。生产端要确认消息是否成功写入Broker失败则重试Broker要开启持久化避免宕机丢数据消费端要等业务处理完成后再提交消费位点而不是拉到消息就立刻提交。三层都做到位才能基本保证不丢消息。重复消费其实比丢失更常见。下游服务处理完消息却因为网络抖动没来得及提交位点消息就被重新投递了。这种场景下幂等设计是关键后面单独讲。消费积压的应对策略倒是简单粗暴增加消费者实例提升消费速率或者临时修改消费者的逻辑把堆积的消息直接丢弃靠后续的定时任务去补数据。但这些都是应急手段最好的方式还是上线之前就要对消费能力做容量评估别等到积压几十万条了才手忙脚乱。3. 几个核心难点的实操方案3.1 缓存穿透、击穿、雪崩的应对缓存这块如果光说“用Redis缓存”那等于没说。面试官一定会在缓存上深挖因为缓存是生产环境最容易出问题、也最能体现候选人对细节把控能力的一环。缓存穿透指的是大量请求查询一个缓存中和数据库中都不存在的数据。因为缓存没有这个Key请求全部打到数据库数据库压力瞬间飙升。这种问题常见于恶意攻击比如用一个不存在的用户ID批量刷接口。解决方案有几种最基础的是把查询结果为空的数据也缓存起来设置一个较短的过期时间比如30到60秒进阶方案是使用布隆过滤器把所有合法ID先过滤一遍不存在的数据直接挡掉连数据库的边都不沾。缓存击穿说的是一个热点Key在缓存过期的瞬间大量并发请求同时又打到数据库。这个和穿透不一样穿透查的是不存在的数据击穿查的是存在但刚好过期了的超级热点。解决方案第一对热点数据设置永不过期由后台异步任务在快要过期时去刷新缓存第二在查询数据库时加互斥锁让同一时刻只允许一个请求去重建缓存其他请求等待缓存被重建后直接读取新值第三用逻辑过期替代物理过期给缓存值加一个逻辑过期时间字段读取时判断是否过期过期则异步刷新并返回旧值。这三种方案各有适用场景但最推荐的还是“逻辑过期加互斥锁”的组合既能保证数据新鲜又不会击穿数据库。缓存雪崩是指大量的缓存Key在同一时间段集中过期导致压力全部落到数据库上数据库应声崩溃。最常见的诱因是缓存设置了相同的过期时间。破解方法很简单在设置过期时间时增加一个随机值比如5到10分钟之间的随机数避免Key在同一秒集体过期。另外如果Redis集群本身宕机了应用层也要有兜底策略比如做缓存的多级部署或者开启限流降级防止所有请求直接打到数据库。我把这三个问题放在一张表里方便你对照记忆问题产生原因核心应对策略缓存穿透查询的数据在缓存和数据库中都不存在空值缓存、布隆过滤器缓存击穿热点Key过期瞬间大量并发打库永不过期、互斥锁、逻辑过期缓存雪崩大量Key同时过期或Redis宕机过期时间加随机数、多级缓存、限流降级3.2 数据库分库分表什么时候分、怎么分数据库的分库分表是很多候选人分水岭式的话题。嘴上说容易但能说清楚“为什么分、怎么分、分了之后怎么办”的人非常少。先说什么时候该分。数据库的瓶颈有两种一种是有大量的写并发比如每秒几万次写入另一种是数据量巨大单表存储过亿甚至几十亿行导致索引深度过大、写入变慢、备份和DDL都痛苦不堪。如果是第一种优先考虑分库把写压力分摊到多个库上如果是第二种优先考虑分表把单表的数据量降下来。分表和分库其实可以按维度组合。水平分库分表就是按照某个关键的拆分键取模或哈希把数据均匀分布到多个库和多张表里。比如订单表按用户ID取模分成64张表分布到4个库里每个库16张表单表数据量立刻下降一个数量级。拆分键的选择是分库分表方案里最重要的决策没有之一。核心原则是让大部分查询能路由到固定的分片。订单系统按用户ID分片那么用户查自己的订单列表就只需要查一个分片但如果运营后台要按订单号查所有用户的订单就要在所有分片上并行查询这就是跨分片查询问题。如果你同时需要按用户ID和商家ID查询就得想清楚哪个是主拆分键另一个维度通过冗余表或搜索引擎来解决。分库分表之后原来单库的简单操作都变得复杂了。自增主键不能用因为多个分片会重复全局唯一ID需要分布式ID生成器常见方案有雪花算法、Leaf、Redis自增配合日期前缀等。跨分片的join不能直接执行需要在应用层做二次聚合。跨分片的事务问题更是头疼已经不能依赖数据库本地事务需要引入分布式事务框架比如Seata的AT模式或TCC方案。还要考虑后续扩容的问题。如果一开始按取模分库分了8个库后面数据涨了要扩到16个库迁移成本极高几乎是重建索引、全量迁移。业界更推荐的做法是使用一致性哈希或者在拆分键设计时预留未来分片数翻倍的余量。还有一种思路是使用中间件层的方案比如ShardingSphere把分库分表的逻辑封装在中间件里应用层改动少但它也有自身的性能开销和复杂度。千万别一上来就分库分表。很多系统在数据量只有几百万、QPS只有几百的时候就开始拆库拆表结果把自己折腾得疲惫不堪。务实的路线是先做读写分离一主多从扛读流量不够了再做垂直拆分按业务模块拆库还是不够才做水平分库分表。每一步都是被流量逼出来的而不是为了架构而架构。3.3 幂等设计与最终一致性高并发系统一定会用到消息队列和异步化而只要存在网络调用和重试机制幂等就是一个绕不开的话题。幂等的含义很朴素同一个操作执行一次和执行多次最终的结果是一致的。比如用户误触了下单按钮发了两次请求系统不应该创建两笔订单支付回调被重复投递了三次订单状态不应该被覆盖为错误状态。接口幂等性设计最常用的方案是唯一键约束。在数据库表上加一个业务唯一键比如订单号加商品ID的组合第二次插入时数据库会因为唯一键冲突而拒绝。但对于更新操作唯一键约束没法直接解决更常见的做法是靠状态机控制。比如订单状态有“待支付、已支付、已发货、已完成”几个状态只有当订单当前状态是“待支付”时才允许支付回调把它改成“已支付”如果已经是“已支付”了再来一次回调就直接忽略。分布式锁也经常用于幂等控制。对于同一个订单的并发操作可以用Redis分布式锁保证同时只有一个请求能进入关键流程其他请求等待锁释放后发现状态已被处理就直接返回。Redisson提供了现成的可重入锁实现但在高并发下要特别注意锁粒度和锁超时时间锁粒度太大容易排队锁时间太短容易提前释放导致并发控制失效。消息消费端的幂等同样重要。消费者从MQ拿到消息处理成功后还没来得及提交位点MQ就重新投递了这条消息。此时消费者应该能识别出这条消息已经被处理过否则就会重复更新数据。解决方案有几种比较常用的是在消息体中携带业务主键消费时先查一下Redis或数据库发现已处理就直接跳过。最终一致性这个概念很多人能说出“BASE理论”和“柔性事务”但你要能落到实际设计中。比如支付系统用户支付成功后支付服务更新自己的支付订单表然后发送一条支付成功消息给订单服务订单服务消费消息后把订单状态改成已支付。这个过程里存在短暂的时间窗口订单服务可能还没消费到消息用户已经看到支付成功但订单还是待支付这就是短暂的不一致。系统必须保证的是这个状态最终会变成一致并且在这个过程中不会有业务错误发生。实现最终一致性的核心是消息投递的可靠性加上消费的幂等性。投递保证消息不丢幂等保证重复投递不产生副作用两者配合最终一致性就有了落地基础。3.4 大事务与锁的优化数据库层面的另一个常见性能杀手就是大事务。一个事务包含大量SQL执行时间长持有的数据库连接和行锁锁定时长就长。在高并发下大量线程会阻塞在锁上造成连接池耗尽系统整体瘫掉。大事务常见的优化手段第一是缩小事务边界。事务只包围必要的写操作把读操作、远程调用、消息发送这类耗时操作移出事务。很多人习惯在Service方法上直接加Transactional结果一个更新操作中间调了一次外部HTTP接口整个事务挂起两秒这段时间内所有涉及同一行数据的操作全部排队。第二是尽量避免事务中调用RPC或MQ发送。外部调用存在不确定性网络抖动、超时重试都会延长事务时间。稳妥的方案是先提交本地事务再发消息如果消息发送失败则通过本地消息表或事务消息机制补偿。第三是注意锁的粒度。悲观锁有性能瓶颈很多读多写少的场景完全可以用乐观锁替代。比如库存扣减用“UPDATE t SET stockstock-1 WHERE id? AND stock1”这种CAS式SQL受影响行数为0就说明库存不足不需要显式加锁性能提升非常明显。第四是热点行的处理。像秒杀场景所有请求都会去update同一个商品的库存行这个行就是热点行再好的数据库也扛不住几百个并发同时更新同一行。业界常见的解法是把库存拆分成多个子库存比如100个库存拆成10个库存桶每个桶10个库存请求随机路由到其中一个桶去扣减这样单个行的写并发瞬间降了一个量级。4. 一个真实场景的架构落地推演4.1 场景设定电商大促下的订单系统光讲理论和框架很多读者还是觉得隔了一层。所以这一章我带大家完整走一遍一个真实场景电商平台大促期间的订单系统设计。业务背景是这样的平台要做一场促销活动预期参与用户50万活动开始后30分钟内是下单高峰用户下单之后需要扣减库存、生成订单、通知仓库发货、推送给用户。支付部分由第三方支付系统承接订单系统只需处理支付回调并更新状态。先做量化分析。50万用户假设平均每人点击5次那就是250万次请求。集中在30分钟内发起平均QPS大约是250万除以1800秒约1400。但流量的毛刺效应很明显第一分钟往往是请求高峰按平均值的三倍估算峰值QPS大约在4200左右。按照50%的余量设计订单系统的目标承载量至少要到6000 QPS。数据库的量级也很关键。30分钟内预计生成20万笔订单单表20万数据量根本不算什么数据库的压力其实不在数据量而在瞬间写入并发。6000 QPS打到数据库上MySQL大概率扛不住所以这里必须设计缓存层和异步化不能让全部请求都穿透到数据库。4.2 容量估算与资源规划基于上面的量化结果我们来算算需要多少资源。首先是应用层。假设单台应用服务器经过优化后能支撑1000 QPS这是一个比较保守的估算值实际要看业务复杂度那么6000 QPS就需要至少6台应用服务器再加上活动期间还要应对流量毛刺和单台故障后的快速替换建议直接上8台并配置好自动伸缩策略。这就是容量从哪来的答案不是拍脑袋而是从QPS和单机能力推导出来的。其次是Redis层。下单过程中的库存校验、用户Token校验、防重复提交校验这些高频读操作全部走Redis。6000 QPS的读操作单节点Redis完全扛得住但为了主从切换的高可用至少部署一对主从。库存数据需要提前预热到Redis里避免活动开始的一瞬间大量请求穿透到数据库。然后是消息队列。库存扣减和订单创建应该异步化。用户下单请求先写MQ后端消费者按数据库的承受能力把消息拉出来处理。假设消息消费者单机每秒能处理200条消息那么20万笔订单除以1800秒平均每秒只有111条消费者单机就够用了但考虑到活动高峰第一分钟的下单量可能是平均值的几倍建议上3到4个消费者实例。最后看数据库。20万笔订单的写入按峰值每秒6000 QPS的穿透量算单库根本扛不住。但通过MQ削峰后真正打到数据库的写请求已经降到了每秒几百的级别单库主从就能支撑。不过为了保险起见可以把订单表按用户ID分成8张表分布到两个库每库4张表这样单库单表的写入压力都在合理范围内。这套容量规划做完总体的资源需求是8台应用服务器、一套Redis主从、3到4个MQ消费者实例、2个数据库实例。这个规模对中小型电商项目来说非常合理既不会资源浪费也能扛住活动压力。4.3 缓存方案落地库存预热与防超卖库存是秒杀类系统最敏感的数据也是考察候选人对数据一致性理解深度的最佳切入点。库存的缓存方案是这样的活动开始前先把商品库存总数加载到Redis中Key的设计可以是“stock:{商品ID}”Value就是剩余库存数量。用户下单时先通过Redis的原子操作做预扣减比如用Lua脚本执行“DECR stock:{商品ID}”如果扣减后的值大于等于0说明库存还有请求继续往下走如果小于0说明库存没了直接返回“已售罄”。这里的关键点是库存扣减要用Lua脚本或Redis原生的原子操作不能在应用层先GET再SET因为并发请求下这两个操作之间会有时间窗口极端情况下100个请求同时GET到库存还有1个然后同时DECR导致超卖。Lua脚本把检查和扣减在Redis内部完成原子性就有保障了。库存Redis扣减成功之后再发一条“库存扣减成功”的消息给订单服务由订单消费者真正去数据库里生成订单并把Redis的扣减结果同步到数据库的库存表里。数据库层面的库存更新使用“UPDATE stock SET stockstock-1 WHERE product_id? AND stock 0”这种条件更新保证数据库最终一致且不会超卖。如果有用户下单后又取消订单那么需要异步把库存加回来同时要处理Redis和数据库的一致性顺序。我的经验是先把数据库的库存加回来再更新Redis缓存这样即使Redis更新失败一段时间后还能通过过期策略或后台任务补偿数据库永远是最准确的数据源。4.4 压测与瓶颈定位方案上线前不做压测等于裸奔。我自己经历过的惨痛教训是某个活动方案预演得头头是道实际压测到一半数据库连接池先被打满才发现连接池参数从没调过。所以压测不是可选动作而是高并发系统上线前必须做的一道工序。压测工具有很多选择JMeter是最常用的开源工具支持图形化配置和分布式施压如果团队对性能工程要求高可以上Locust或者wrk。我个人的建议是JMeter做业务场景压测wrk做纯接口性能测试各司其职。压测过程分三步走。第一步是基线压测在没有任何缓存和优化的情况下跑一轮记录系统天然的处理能力这一步是后续所有优化的对照基准。第二步是目标压测开启缓存、异步、限流等全部优化后按设计目标QPS压测观察各层的响应时间、错误率、资源占用情况。第三步是极限压测把压力持续提升直到系统出现明显错误记录这个临界点它就是系统的真实上限。压测中最常见的瓶颈定位方式是在压测过程中同时看四个维度的指标CPU利用率、内存占用率、GC日志、数据库慢查询。CPU如果跑满多半是业务逻辑里有一次大量计算或者正则表达式等CPU密集型操作内存涨得很快且GC频繁多半是对象创建过多或本地缓存容量设置不合理数据库慢查询增多说明SQL走了全表扫描或索引失效连接数耗尽说明连接池参数设置有问题或者有连接泄漏。压测还有一个容易踩的坑压测结果和生产数据量级不一致。因为压测环境的数据库里可能只有几万条数据而生产环境已经上亿条同样的SQL在两种环境下的执行计划是不一样的。所以压测前一定要把数据量补齐到和生产接近的水平否则压测结论没有任何参考价值。5. 面试中最常见的六个致命错误5.1 无脑堆组件讲不清为什么我在前面反复强调过面试官最反感的就是背名词。你说你用了Redis、MQ、分库分表、微服务、读写分离那每一个组件背后的业务场景是什么选型时的对比评估是什么不用它行不行如果换一个方案会怎样这些问题答不清楚就说明你只是在“抄”别人的架构没有自己的理解。正确的方式是讲清链路。比如这样回答这个系统的读请求占比90%所以我们把读流量用Redis做了一级缓存Nginx本地缓存做二级缓存系统有突发流量所以我们用MQ做削峰数据库数据量预计两年后过亿所以我们按用户ID做了水平分库分表。每一句话都对应一个业务诉求这才叫架构设计。5.2 缓存一致性一笔带过“我用Redis缓存了订单数据更新的时候先更新数据库再删缓存”这个回答只能算及格。面试官一定会追问删除缓存失败了怎么办删了缓存之后正好有请求读到了旧数据怎么办数据库更新成功了但Redis删缓存这个动作还没执行时并发请求会不会读旧值完整回答需要做到三步。第一更新操作先更新数据库第二删除缓存注意是删除而不是更新缓存因为更新缓存可能引入并发写覆盖问题第三如果删除失败通过延迟双删或者监听数据库binlog的方式异步补偿删除。更稳妥的方案是使用Canal监听MySQL binlog只要数据库数据变了就自动失效对应缓存从机制上抹掉了手动删除的不可靠性。5.3 只讲技术不讲成本架构设计里没有银弹任何方案都是有代价的。你用了分库分表就要付出数据迁移、跨分片查询、分布式事务的复杂性你用了消息队列就要处理消息丢失、重复消费、积压告警你上了微服务就要面对链路追踪、配置管理、部署运维的复杂度。面试时主动谈成本会让面试官觉得你不只是个技术人员还有工程判断力。比如你可以说我们现在的量级其实用单体加缓存就够了但考虑到未来半年的快速增长我们提前把订单模块独立出来做成服务这样后续扩展成本更低。这话一讲说明你在架构和业务之间找到了平衡点。5.4 忽略一致性一谈到并发就崩溃高并发和强一致本身就是矛盾的。很多候选人谈起高并发头头是道一被问到数据一致性就闪烁其词这会让面试官对你的方案产生巨大怀疑。因为在高并发架构里一致性问题不是例外而是常态。你要主动解释清楚自己的系统属于哪一类一致性模型。电商订单系统这种场景允许短暂的最终一致但绝对不能超卖和丢单支付系统需要强一致涉及资金的扣减必须保证要么全成功要么全失败。先讲清楚业务的一致性要求再选合适的技术手段这才是有说服力的回答。5.5 说不清容量判断和扩展路径不少候选人能讲清楚当下系统的运行机制但对未来的扩展路径完全没有概念。面试官喜欢问“如果流量再涨十倍你的系统哪一层会先撑不住你怎么处理”回答不上来说明你只是在描述现状而不是设计系统。正确的回答框架是先判断瓶颈层。应用层可以先扩容加机器Redis如果内存不足可以做集群分片数据库如果读压力大就加从库写压力大就分库分表消息队列如果消费速度跟不上就加消费者实例。每一层都有对应的扩展手段这才叫可扩展架构。5.6 不关注监控与自动恢复高并发系统不是说设计完了就能稳定运行它需要被持续观测和维护。线上出一个问题如果全靠人工去看日志排查那说明你的系统还没有完善的监控体系。成熟的方案是三点第一个是指标监控Prometheus加Grafana是标配重点监控QPS、RT、错误率、JVM内存和GC、MySQL慢查询和活跃连接数第二个是链路追踪SkyWalking或Jaeger把一次请求的完整调用链串起来定位慢节点和高延迟服务第三个是告警通知配合AlertManager配置好阈值告警通过短信、企业微信等渠道及时推给值班同学。再加一个流量防护组件Sentinel配置好降级规则和熔断规则能让系统在异常流量下自动保护自己。6. 写在最后的个人经验这个题目带过很多候选人也陪跑过很多团队从单体到高并发的演进过程。我自己的体会是架构设计的能力一定是在项目里磨出来的不是在面试题库里刷出来的。如果现在有人问我准备高并发架构面试最有效的方法是什么我的建议很简单找一个你身边的真实业务系统先画出它当前的架构图标出每一层是什么组件、处理什么流量、有哪些隐患然后尝试给它提出一套改进方案最后推演改进之后各层能够承载的QPS变化。这套练习做下来你对高并发架构的理解一定比背一百道题管用。还有一个面试时很实用的小技巧当你被问到架构方案时别急着说结论先说场景和数字。你说“我们这个业务峰值QPS大概五千读多写少库存类数据需要强一致”比直接说“我用Redis加MQ”高级得多。面试官听的是你的分析链路不是你的组件清单。高并发架构没有一步到位的银弹它是流量逼迫、技术选型、架构演进的长期过程。把每一层的原理吃透把每一个方案的代价想清在不断的实测和复盘里积累自己的判断力这才是能陪你走很远的核心能力。