小米服务端校招主观题复盘:高并发与系统设计核心考点全解析 📅 发布时间:2026/8/29 7:26:24 👁 浏览次数: 2018年秋招那会儿我把小米服务端工程师的笔试主观题从头到尾啃了一遍。整张卷子没有选择题全是需要现场组织语言、画架构、写方案的设计题。说实话那是我秋招遇到的第一份“考思路比考记忆多”的卷子。现在回看这套主观题其实把服务端开发最核心的知识域都串起来了高并发设计、网络链路、缓存一致性、海量数据处理。本文把这套主观题的完整复盘写出来每道题都拆到考察点、答题框架、追问预案三个层次适合正在准备后端或服务端校招岗位的读者也适合刚入行、想系统建立服务端知识体系的朋友收藏。1. 主观题全景小米服务端校招到底考什么1.1 主观题和客观题最大的区别在哪客观题考“你知道不知道”主观题考“你会不会用”。服务端工程师的主观题尤其明显它不会直接问你“TCP三次握手是哪三次”而是给你一个业务场景比如“设计一个支持高并发的短链接服务”让你从零开始把它讲清楚。这时候面试官和判卷人真正想看的不是你背了多少八股而是三个维度。第一是完整性。同样一道短URL设计题有人只会说“用哈希生成短码”有人能从发号器、存储选型、缓存策略、重定向状态码、压测预估讲到监控报警。覆盖的链路越完整说明你对服务端系统的全局认知越强。第二是取舍感。系统设计里没有“银弹”只有场景匹配。比如短链接的读请求远多于写请求就适合缓存和只读从库秒杀场景的写请求集中在热点商品上就适合队列削峰和库存前置。能不能说清楚为什么在这个场景选这个方案是主观题拉开分差的关键。第三是表达能力。方案在脑子里是一团乱麻还是能按“需求分析、量级估算、架构设计、核心细节、追问预案”的顺序讲出来直接决定了判卷人对你工程素养的判断。我见过不少技术水平不差的人因为表达混乱丢了分非常可惜。1.2 当年这套题覆盖的知识域速览主观题合集里一共六道题每道都对应一个独立的知识域。我按当年的题目顺序整理成了下面这个对照表方便后续逐个展开。题目考察方向核心知识点设计一个短URL服务系统设计发号器、Base62编码、哈希冲突、301与302设计秒杀系统的服务端方案高并发限流算法、MQ削峰、库存扣减、乐观锁从输入URL到页面展示发生了什么网络综合DNS、TCP、HTTP、浏览器渲染、缓存缓存与数据库一致性怎么保证分布式Cache Aside、延迟双删、binlog补偿从海量日志中统计访问次数Top100的IP海量数据分而治之、Hash分片、堆排序服务端接口幂等性怎么设计接口设计幂等键、唯一索引、状态机、重试机制这套题放到现在来看也完全不过时。这几年各大厂的校招面经翻来翻去题目形态可能改了包装但内核依然是这六块。所以与其到处找题库刷数量不如把这几类问题的思考框架真正建立起来。我当时拿到这套题之后的第一反应是题目本身不难难的是在有限时间内把答案组织得足够完整。这也是主观题最磨人的地方。2. 高频系统设计题短URL服务怎么一步步拆解2.1 拿到系统设计题先别急着写方案短URL服务当年排在主观题第一位也是几乎所有服务端面试里最高频的系统设计题目。题目一般这么描述设计一个短链接服务可以把长链接变成短链接用户访问短链接时能跳转到原始长链接要求支撑海量访问数据不能丢。这道题最忌讳的就是上来直接画架构图。正确的打开方式先拆需求。核心功能只有两个生成短码、跳转还原。非功能需求有三个高可用、高性能、数据可靠。接下来做量级估算比如每天新增短链接1000万条那就意味着一年存储量在36.5亿条左右MySQL只要设计好分表完全能扛住写入访问量的读请求远大于写请求比例粗略按100比1来算峰值QPS可能到几十万甚至上百万这时候必须上缓存。量级估算的意义在于它会倒逼你做出合理的技术选型。如果你没做估算就直接说“用Redis存所有短链接”这是错误的——因为Redis内存太贵不划算。估算完之后你会发现正确的存储选型应该是“全量数据放MySQL热点映射放Redis”这样成本和性能都是可控的。2.2 短码生成方案对比发号器为什么是首选短码怎么生成是这道题的第一个核心决策点。市面上常见的方案有哈希、随机数和发号器三种我当年在面试中把三种都讲了最后给出了选型结论。哈希方案的思路是把原始长URL取MD5或CRC32值截取前几位作为短码。优点是本地计算快、不需要额外存储依赖但缺点是哈希冲突不可控一旦两个不同长URL算出相同短码要么拒绝生成要么重试重算实现起来麻烦而且短码长度也不好控制。随机数方案是随机生成一组字符作为短码实现最简单但每次生成都要查一次数据库确认没有撞码生成效率低存储的短码索引也大。发号器方案则完全不同用数据库自增ID或分布式ID生成器拿到一个全局递增的ID然后把这个ID做Base62编码。因为ID本身是唯一的编码后的短码天然不会冲突同时还能反向解码还原ID连索引查询都省了。发号器方案之所以是首选是因为它同时解决了唯一性、短码长度和查询效率三个问题。下面这个Python示例是我当年整理笔记时写的展示了最核心的Base62编码逻辑。ALPHABET 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ def base62_encode(num): if num 0: return ALPHABET[0] res [] while num 0: num, rem divmod(num, 62) res.append(ALPHABET[rem]) return .join(reversed(res)) def base62_decode(code): num 0 for ch in code: num num * 62 ALPHABET.index(ch) return num注意看编码结果ID等于10000000时编码出来的短码长度只有4到5位比纯随机字符串短得多用户体验更好。而解码时只需要遍历字符串逐位计算时间复杂度是O(n)性能极高。2.3 存储、缓存和重定向的细节别漏掉短码方案定了之后存储表结构基本是固定的id、short_code、original_url、created_at其中short_code加唯一索引。这里有个细节如果直接用唯一的short_code作为主键那在数据量大之后分库分表的键选择会更方便可以把short_code作为分片键这样同一个短码的查询永远只落在同一个分片上。查询跳转的流程是典型的Cache Aside模式先查Redis命中直接返回长URL没有命中就回源MySQL查询查到后回填Redis并返回Redis里查不到且数据库也没有就返回404。回填的时候记得设置过期时间比如一到两天避免冷数据长期占用内存。重定向状态码选301还是302是这道题里最经典的追问点。301是永久重定向浏览器会缓存跳转结果第二次访问同一个短链接时浏览器干脆不发请求直接跳到长URL服务端压力小但拿不到点击量数据。302是临时重定向每次访问都会请求服务端方便统计点击数据但会给服务端带来更多请求量。业务方如果对点击数据敏感比如营销活动想看转化率就选302如果完全不在意统计只求省资源301也可以。面试里建议说“大多数业务场景选302因为统计优先”。2.4 追问环节的高频深挖点面试官不会满足于你讲完主体方案大概率会追问三个方向。第一个方向是并发安全发号器的ID并发重复怎么办答案是数据库自增ID天然保证并发唯一如果担心数据库单点瓶颈可以用号段模式一次性从数据库取一批ID到内存比如每次取1万个发完再去取数据库压力直接降几个数量级。第二个方向是安全问题短码只有几个字符暴力枚举怎么办可以给短码加随机位打散规律性同时对同一个IP的访问频率做限流防止恶意遍历。第三个方向是雪崩问题缓存大面积失效怎么办Redis的key要设置合理过期时间加随机偏移数据库层要有降级预案比如限制只允许一部分节点回源其他节点直接返回提示页。这道题只要把“需求拆解、量级估算、发号器、Cache Aside、301/302、追问预案”这六步完整走一遍基本就能拿到基准以上的分数。它的套路其实就是所有系统设计题的通用套路。3. 高并发场景题秒杀系统的服务端方案怎么答3.1 秒杀的本质不是卖货是流量管控秒杀是服务端面试里另一道经久不衰的场景题。题目通常这样出某个商品库存1万件开放抢购后瞬间涌入百万级请求请设计服务端方案要求不能超卖、不能崩溃、用户体验尽量平滑。拿到这种题第一个要扭转的思维是不要试图让所有请求都成功。秒杀系统的本质是一次大规模的流量管控1万件库存对应的有效请求只有1万个剩下的99万个请求注定要失败系统要做的不是让它们全部进入业务流程而是让绝大部分请求在入口处就被拦截掉。这个思维可以泛化到所有服务端高并发场景昂贵资源数据库、磁盘IO、下游第三方接口永远不应该直接暴露在全部流量之下。我当时把这套思路总结成三层拦截模型网关层挡掉非法请求和超出阈值的流量应用层限流和排队数据层只用真正有资格的请求去触碰库存。很多方案翻车往往是因为直接把用户请求打到了数据层导致数据库连接池被瞬间打满整个服务连带宕机。3.2 服务端限流算法选型从固定窗口到令牌桶限流是秒杀系统的地基也是服务端接口保护的基本功。面试中你至少要能讲清楚四种限流算法的适用场景和缺陷。固定窗口计数器是最简单的方式每秒一个窗口窗口内计数超过阈值直接拒绝。问题在于临界突变0到1秒的窗口和1到2秒的窗口各自允许100个请求那在临界点前后可能瞬间放过200个请求流量不够平滑。滑动窗口在固定窗口的基础上把窗口细分成多个小格子滚动统计能有效消除临界突变但实现上要维护时间轮占用更多内存。漏桶算法像一个漏斗请求先进入桶里以固定速率流出不管上游流量多大下游拿到的始终是匀速的。它适合保护下游系统但代价是请求延迟被拉高。令牌桶算法则是以固定速率往桶里放令牌请求来的时候必须拿到令牌才能通过桶里最多攒满一定数量的令牌所以它允许一定程度的突发流量性能好也是实际工程里用得最多的方案。下面是一个基于Redis计数器做简单限流的伪代码生产环境可以换成Lua脚本保证原子性。// 基于Redis INCR的固定窗口限流伪代码 String key rate_limit: System.currentTimeMillis() / 1000; Long count redisTemplate.opsForValue().increment(key); if (count ! null count 1) { redisTemplate.expire(key, 2, TimeUnit.SECONDS); } if (count ! null count MAX_QPS_PER_WORKER) { throw new BizException(访问过于频繁请稍后再试); }面试中可以这样总结选型并发量小、实现简单选固定窗口下游保护优先选漏桶既要扛突发流量又要稳定输出选令牌桶。秒杀场景下接口层用令牌桶下单入口用队列削峰两者配合。3.3 库存扣减怎么避免超卖限流只是挡掉了大部分流量真正处理剩余的“有效请求”时最怕的是超卖。库存扣减的经典方案是数据库行锁SQL长这样UPDATE stock SET count count - 1 WHERE product_id ? AND count 0;关键在于count 0这个条件在并发事务中MySQL的行锁会让同一行记录的操作串行化只有一个事务能把count减到0其他事务因为条件不满足更新行数为0不会真正扣减所以天然能防超卖。但它的缺点是大量请求同时竞争同一行的锁数据库压力非常大吞吐量受限。另一种思路是乐观锁。给库存表加version字段更新时带上版本号版本号变了就说明被别人改过重试或返回失败。秒杀场景下库存是热点数据乐观锁重试次数会非常多体验反而不好所以更推荐的做法是Redis预扣减。所谓预扣减就是先把库存同步到Redis用DECR命令原子减一减完大于等于0才算成功然后立刻返回用户“抢购成功”。真正的数据库库存扣减通过MQ异步消息来完成而不是让用户请求同步等待。Redis的单线程模型天然串行化性能远高于数据库行锁能抗住几十万的扣减QPS。这里要注意的是Redis扣减成功但异步消息投递失败会造成Redis和数据库库存不一致所以必须引入对账任务定期比对两边库存发现差异就补偿。3.4 秒杀方案的通用回答模板这道题回答时我建议按下面这个顺序来。第一需求分析明确库存量、预估峰值QPS、超卖容忍度。第二前端拦截按钮置灰、动态令牌先把脚本刷量挡掉。第三网关层限流按用户维度、IP维度双重限制。第四应用层排队把真正进入下单流程的请求压进MQ用队列长度控制数据库压力。第五库存扣减Redis预扣减加数据库最终扣减。第六兜底方案对账补偿、人工介入。这套模板不只是为秒杀准备的凡是涉及高并发写入的系统都可以参考。4. 网络综合题从输入URL到页面展示的完整链路4.1 全链路七层梳理这道题几乎是服务端面试的必考题因为它能一次性考察网络基础、操作系统、Web服务、浏览器原理多个领域。题目描述很简短用户在浏览器输入一个URL按下回车到页面完整展示中间发生了什么。完整的回答链路我习惯分成八个阶段。第一阶段是URL解析浏览器提取协议、域名、端口、路径和查询参数。第二阶段是缓存查找浏览器缓存、系统缓存、路由器缓存甚至DNS服务器缓存都查一遍有没有对应的IP。第三阶段是DNS解析把域名解析成IP这个过程中会涉及本地DNS服务器、根DNS服务器、顶级域名服务器、权威域名服务器的递归或迭代查询。第四阶段是TCP连接建立三次握手。第五阶段是发送HTTP请求如果用了HTTPS还要先经历TLS握手协商密钥。第六阶段是服务端处理请求经过负载均衡、应用逻辑、数据库查询后返回响应。第七阶段是浏览器解析HTML、CSS、JavaScript构建DOM树和渲染树。第八阶段是连接关闭或者保持长连接复用。这道题能拿高分的要点不在于把八阶段背下来而在于每个阶段都能讲出几个“别人不常提的细节”。4.2 每个阶段容易忽略的细节DNS阶段容易被忽略的点是缓存和TTL。DNS查询结果并不是每次都全链路递归每一层都有缓存缓存时间由TTL控制所以一次域名解析可能只要几毫秒也可能因为缓存过期需要重新走完整递归链路而耗时几百毫秒。面试官如果问“为什么首次访问慢、后续访问快”答案就在这里。TCP阶段最经典的问题是为什么三次握手而不是两次。答案很简单三次握手能让双方都确认自己的发送能力和接收能力是正常的。第一次握手客户端确认自己发送能力正常、服务端接收正常第二次握手服务端确认自己发送能力正常、客户端接收正常第三次握手客户端再确认一次双方收发都正常顺便告诉服务端可以开始发数据了。两次握手的话服务端无法确认客户端的接收能力。HTTP阶段值得展开的是协议演进。HTTP/1.1引入了Keep-Alive长连接和管线化但管线化存在队头阻塞问题前一个请求不结束后一个响应就得等着。HTTP/2通过多路复用解决了HTTP层的队头阻塞但TCP层的队头阻塞依然存在。HTTP/3干脆改用基于UDP的QUIC协议彻底绕开了TCP层的队头阻塞问题。这一段如果能在面试中主动讲出来面试官对你的网络功底会明显加分。还有HTTPS。如果URL是https开头那在TCP连接建立之后还要额外做TLS握手客户端发送支持的加密套件列表服务端返回证书客户端验证证书链并生成会话密钥双方协商加密方案。这个环节最容易答漏但它恰恰是现代服务端最基础的安全能力。4.3 面试官的挖坑方式长什么样这类题目答完主体链路之后面试官一般会沿着细节继续追问。比如输入一个不存在的域名会怎样答案是DNS解析失败浏览器显示“无法访问该网站”服务端侧根本收不到任何请求所以排障第一步应该先确认域名解析记录有没有配错。再比如如果目标服务器的端口根本没有进程监听客户端会收到什么答案是RST包TCP连接直接被重置而不是正常的四次挥手FIN包。这个问题的意义在于区分“端口不可达”和“连接正常关闭”是两种完全不同的状态。又比如为什么页面里有大量图片资源时HTTP/1.1需要建立多个连接因为HTTP/1.1的Keep-Alive虽然是长连接但同一个连接同一时刻只能处理一个请求浏览器为了并行加载资源只能开多个TCP连接而HTTP/2的多路复用允许在一个连接上同时并发多个请求。这些问题每个都是知识点不需要猜题只要把链路理解透了怎么问都能接住。5. 缓存与数据库一致性判断依据和工程取舍5.1 缓存三大问题穿透、击穿、雪崩服务端面试聊缓存绕不开这三个经典问题。穿透是指用户查询一个根本不存在的数据缓存里没有数据库里也没有请求每次都穿透到数据库如果被恶意脚本大量发起数据库响应就会被打爆。解决办法是缓存空值给不存在的key也写一个短暂的空缓存或者用布隆过滤器在请求到达数据库之前判断key是否存在不存在直接返回。击穿是指某个热点key在过期的一瞬间大量请求同时发现缓存没有数据一起打到数据库上。解决办法是热点key不设置过期时间或者把过期时间拉长同时用后台任务异步更新缓存保证旧数据在过期后依然可以被请求命中而不是让请求穿透。雪崩是指大量key在同一时间段内集中过期导致数据库瞬间承受大量请求。解决办法很简单每个key的过期时间加一个随机偏移量比如在五天到七天之间随机让过期时间自然错峰。这三大问题在答题时很多人会混淆我的记忆方法是穿透是不存在的key击穿是单个热点key过期雪崩是批量key过期。三个问题对应三个解决思路别混。5.2 Cache Aside模式与延迟双删的细节缓存读写策略里Cache Aside是最主流的一种也是服务端日常开发中最常被问到的一种。读流程是先查缓存命中直接返回没命中就查数据库把结果回填缓存再返回。写流程是先更新数据库再删除缓存。为什么是删除而不是更新缓存原因有两层。第一层更新缓存成本高于删除缓存。更新缓存要重新序列化对象、写Redis而且可能写入一个之后永远不会被读到的冷数据删除只是O(1)操作。第二层并发环境下更新缓存容易产生旧值覆盖新值的问题。两个请求同时写同一个数据后到的请求先写了数据库前到的请求后写了缓存缓存里存的实际上是旧值这比删除缓存引发的不一致更麻烦。那么先删缓存再更新数据库行不行也不行。假设请求A删除缓存后还没来得及更新数据库请求B来读缓存发现没数据回源数据库读到了旧值并回填缓存这时请求A才更新数据库缓存里就永远留下了旧值。所以标准做法是先更库再删缓存。后来又有人提出延迟双删先删缓存、更新数据库、等几百毫秒再删一次缓存把中间窗口期被重新填进去的旧值清掉。延迟双删能降低不一致概率但在极端情况下如果第二次删除失败该不一致还是不一致所以更可靠的方案是订阅数据库binlog解析出变更事件后删除对应缓存删除失败就重试。这个方案复杂度高但工程上最可靠。5.3 一致性要求的业务分级和选型逻辑面试中说到缓存一致性不要只背方案要能让面试官看到你的取舍判断。一致性要求是分级的。普通业务数据比如用户昵称、文章阅读数能接受秒级别的暂时不一致用Cache Aside就够了。强一致要求的数据比如支付状态、订单状态缓存只读不写或者直接不走缓存所有读写都落数据库。中间态的数据比如购物车内容允许短时间弱一致可以用延迟双删或者binlog补偿。还有一个容易被忽略的维度是读写比例。同一个数据如果读多写少缓存命中率很高先更库再删缓存的代价就很小如果写非常频繁那每次写都要删一次缓存下一次读又必然miss回源缓存反而成了负担。这种情况下要么考虑缓存不入库要么设计更精细的淘汰策略。面试官问“你项目里怎么处理缓存一致性的”你只需要说清楚业务场景、读写比例、能接受的不一致时长方案以及上线后怎么验证和补偿这个答案就是有说服力的。6. 主观题答卷的常见坑与复习路线6.1 当年我们最容易踩的几个坑刷主观题和刷选择题有一个很大的差别主观题没有标准答案但也没有人帮你批改所以自己很难发现答题逻辑上的漏洞。我当时整理了一套常见坑点速查表是跟身边好几个一起准备秋招的同学对答案时总结出来的。常见问题典型表现如何避免只答方案不答原因说“用Redis做缓存”但不解释为什么用Redis每个技术选型都补一句“因为……所以……”不做量级估算直接画架构图没有数据支撑先算QPS、存储量再选型忽略非功能需求只讲功能逻辑不说高可用和监控加上故障预案、监控报警、降级方案链路题漏层从URL输入到展示漏掉DNS或TLS按阶段逐层背诵一个不漏架构方案过度设计一个短链接服务上了微服务加消息队列方案匹配量级小系统用小方案被追问就懵准备过的问题能答换个角度就卡住针对每个方案准备三个追问预案这里面最亏的是“被追问就懵”。主观题本身就是面试官考察你的窗口追问越多说明他越感兴趣一旦你露出“没想过这个问题”的状态观感就会打折扣。所以我在整理每道题的时候都会强迫自己追加三个反向问题比如“这个方案的瓶颈在哪”“这个环节如果挂了怎么兜底”“数据量再大10倍还能撑住吗”。6.2 服务端校招主观题的复习三层结构主观题看起来千变万化其实复习层次可以拆成三层。第一层是地基TCP/IP、HTTP协议、数据结构、操作系统、数据库原理。这些是离散的知识点可以用思维导图串起来每天过一遍。第二层是中间件Redis、MySQL、Kafka、Nginx不用精通源码但至少要知道每个组件的核心数据结构和适用场景比如Redis的跳表、MySQL的B树和事务隔离级别、Kafka的分区与消费者组、Nginx的负载均衡策略。第三层是系统设计实战从短URL服务、秒杀系统、消息队列、附近的人、排行榜这类经典题入手每道题都按“需求分析、量级估算、架构设计、核心细节、追问预案”五步闭环练一遍。我当时复习的时候给自己定了一条铁律每道题必须能脱稿讲满十分钟中间不能停顿去翻笔记。主观题本质上是一门“讲出来”的学问你脑海里想得再清楚表达不出来在面试里就等于没有准备。对着镜子讲、录音下来自己回听是最笨但最有效的练法。这套题集里的六道题我后来反复看了很多遍每次看都有新的理解。尤其现在工作以后做服务端研发遇到线上接口抖动、缓存击穿、库存对不上账这些问题时经常会想起当年笔试里那些看似遥远的问题。主观题真正考察的从来不是你记住了多少标准答案而是当你在面对一个从未见过的系统问题时有没有一套稳定的拆解方法论。一旦建立了这套方法不管技术栈怎么更替它都不会过时。