
最近把压箱底的《小米2018秋招服务端工程师主观题合集》翻出来重新做了一遍发现一个很有意思的现象当年觉得“偏、难、怪”的主观题放到今天看很多都能直接映射到业务系统里踩过的坑上。小米的秋招笔试题在圈内口碑一直两极分化喜欢的觉得它考得扎实不喜欢的觉得主观题太多、答案怎么编都行。但真正经历过的人会明白主观题恰恰是最能拉开差距的部分——它考察的是你对问题的理解深度而不是单纯的知识记忆宽度。这篇文章不打算给你贴原题答案而是想从“复盘一份服务端主观题集”的角度把主观题拆开来看它到底在考什么、为什么这么问、以及用什么样的答题思路才能拿到分。适合准备大厂服务端岗位校招的应届生、打算跳槽的初级服务端开发以及想系统梳理自己服务端知识体系的同学。如果你以为主观题只是“简答题”那这篇文章大概能帮你少走不少弯路。1. 主观题在服务端笔试里到底考什么1.1 客观题考广度主观题考工程判断力先对比两类典型题目。客观题往往是这个画风ThreadPoolExecutor有哪几种拒绝策略A. AbortPolicyB. DiscardPolicy……这种题目考的是“你知道不知道”。而主观题通常长这样某接口高峰期耗时从 50ms 涨到 2s服务端 QPS 明显下降你会从哪些维度排查请给出排查思路和可能的优化手段。对比一下就能发现主观题根本没有标准答案它考的是“你碰到不确定的技术问题时会不会动脑子”。小米这类公司做服务端招聘最不缺的就是背过各种八股的人缺的是能在一个模糊问题上给出有依据、可执行方案的人。所以我复盘这份题集时最大的体会是主观题不能当简答题准备要当成“小型系统设计题”来准备。我见过太多人在主观题上翻车不是不懂某个技术点而是答案写得像名词解释“线程池是用来管理线程的”“消息队列可以做异步解耦”。这种回答等于告诉阅卷人你没有真正做过服务端系统。真正合格的答案需要把技术点放进具体业务场景里讲清楚为什么要这么选、代价是什么。1.2 高频考点地图五个避不开的方向结合当年的招聘趋势和这份题集的内容服务端主观题大致分布在五个方向方向典型问法考察目的并发与线程池线程池参数如何设置任务堆积了怎么办考察对并发模型的真实理解缓存与一致性缓存穿透怎么解决缓存和数据库如何保持一致考察读写链路的方案设计能力消息队列与异步化异步削峰时如何保证消息不丢不重考察系统解耦和可靠性设计分布式与一致性分布式锁怎么做分布式事务如何取舍考察多节点场景下的权衡能力网络与高可用TCP 和 HTTP 有什么关系如何做限流降级考察协议基础和稳定性设计注意这五个方向不是孤立的。比如消息队列的题目会牵扯到幂等幂等又会牵扯到数据库唯一键和状态机高可用的题目会牵扯到限流和熔断而限流算法又可能结合 Redis 或者网关来考察。所以答题时不要死背某个知识点要能看到知识点之间的连接线这是主观题复习的核心策略。1.3 主观题背后的四条底层能力我总结下来主观题再怎么换皮核心就是看四点定义问题的能力能不能把一个模糊的描述收敛成一个明确的技术问题。比如“服务变慢了”这句话你得先判断是网络问题、锁问题、数据库问题还是资源耗尽问题。权衡方案的能力方案 A 和方案 B 各有什么代价能不能讲清选型理由。比如数据库读写分离和分布式缓存都能抗读压力但一致性、成本和维护复杂度完全不同。结构化的表达能力答案有没有层次先讲什么后讲什么有没有结论先行。基础知识的迁移能力会不会用 TCP、操作系统、数据结构等底层原理来解释上层系统的现象。只要围绕这四点去组织答案哪怕某个具体方案不是最优解阅卷人也能从你的推导过程中看到潜力得分不会低。反过来如果只背诵一个个孤立的知识点遇到稍微变形的主观题就容易卡壳。2. 并发与线程池主题别只会背参数2.1 线程池参数之间的逻辑关系服务端主观题里线程池几乎是固定嘉宾。有一类经典问法“有一个任务服务核心线程数、最大线程数、队列大小该怎么设置”很多人一上来就背公式“CPU 密集型线程数等于 CPU 核数加 1IO 密集型线程数等于核数乘以 2。”但对主观题来说只背这个结论没有意义必须补充条件推导。我建议答题时按下面的路径走先描述任务类型。CPU 密集型任务线程数不宜超过核数太多否则线程切换会反噬性能IO 密集型任务线程大部分时间在等待可以适当提高线程数具体数值需要根据 IO 等待占比来估算。再说队列的选择。无界队列在流量突发时容易把内存打满有界队列则会触发拒绝策略。关键是要配置一个合理容量并且想好队列满之后怎么办。接着讲拒绝策略。AbortPolicy会抛异常CallerRunsPolicy会把任务退回调用线程执行DiscardPolicy和DiscardOldestPolicy会静默丢弃。具体选哪种取决于业务能不能接受丢弃。一个高分答案应该落到“参数互相制约”上。比如核心线程数设为 8最大线程数设为 32队列容量设为 1000那么任务数超过 8 个后统一进队列队列满后开始创建非核心线程直到 32再满就触发拒绝策略。你把这套流转过程讲清楚阅卷人一眼就知道你是真的理解线程池。对应到代码实际配置可能是这样new ThreadPoolExecutor( 8, 32, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new NamedThreadFactory(order-worker), new ThreadPoolExecutor.CallerRunsPolicy() );这里我特意选了CallerRunsPolicy就是当任务实在处理不过来的时候让调用方线程自己执行任务这样即使队列满了任务也不会被静默丢掉同时调用方线程忙于执行任务自然就起到了一定的反压效果。但要注意这个策略在调用线程本身也很繁忙的高并发场景下可能会拖慢上游接口所以必须配合超时和降级一起考虑。2.2 现场题线程池任务堆积时怎么快速恢复题目通常是“高峰期线程池队列积压了大量任务怎么办”这类题没有唯一答案但答题逻辑很重要。我的建议顺序先用监控数据定位问题再选应急手段最后做长期治理。第一步看队列积压量、任务处理耗时、线程池活跃线程数、CPU、内存和 GC 指标。如果 CPU 还没打满说明瓶颈不在计算能力可能在锁竞争、远程调用或 IO 等待上。如果 CPU 已经打满说明线程数很可能已经过多盲目调大线程数反而会恶化。第二步应急处理。如果任务允许短暂延迟可以适度调大队列容量同时增加消费者数量。如果任务实时性要求高可以走降级把非核心的报表、通知、日志同步任务先停掉。如果积压已经非常严重还可以先做流量切换把一部分流量摘掉让线程池消化存量任务。第三步长期方案。区分核心任务和非核心任务拆分线程池保证核心链路的资源不被非核心业务挤占对下游依赖做超时和熔断把长任务改成异步回调或消息队列避免长时间占住线程导致整个线程池被慢任务阻塞。这道题答得好的关键是不要只丢一个操作动作而是给出“问题分类 应急 治理”三层结构。这个结构可以套用到很多主观题里比如磁盘写满、CPU 飙升、接口超时本质都是“先定位问题影响范围再止血最后根治”。2.3 并发题里常见的失分点结合我批改思路和身边朋友踩坑的经历并发主观题容易丢分的地方主要有这几个只背参数不解释关联。比如只写“核心线程数、最大线程数、队列”没有说明任务提交后的流转流程。忽略了线程池监控。很多答案根本没提如何发现堆积就直接跳到优化方案这等于把“定位问题”这一步跳过了。没有区分任务类型。拿同一个参数方案硬套 CPU 密集和 IO 密集任务一眼就能看出是纸上谈兵。答拒绝策略时只列四种策略的名字没讲业务场景下如何选择以及数据丢失或任务退路怎么处理。我的经验是每次回答涉及线程池最好在答案里用有序列表描述一遍任务提交流转提交任务 → 核心线程未满创建线程 → 核心线程已满放入队列 → 队列已满创建非核心线程 → 非核心线程已达最大值触发拒绝策略。这个流程既不冗长又能非常明确地展示你对线程池运作机制的理解。3. 缓存与数据一致性主观题的高频陷阱区3.1 缓存穿透、击穿、雪崩要分开答这三个概念在服务端面试里经常被拿出来单独问也经常被混在一起。很多人答题时容易把缓存穿透和缓存雪崩的方案混着说这是比较影响评分的。要分开答。缓存穿透请求的数据在缓存和数据库里都不存在导致每次请求都直接打到数据库。核心应对思路有两个一是用布隆过滤器在缓存前过滤掉不存在的 key二是缓存空值并设置短过期时间。缓存击穿一个热点 key 过期的瞬间大量请求同时访问数据库。核心应对思路是互斥锁或逻辑过期。互斥锁保证同一时刻只允许一个请求回源其他请求等待逻辑过期则是在缓存中存逻辑过期时间实际 key 不删除后台线程异步刷新读取时若发现逻辑过期就触发一个线程去更新。缓存雪崩大量 key 在同一时间过期或者缓存集群整体宕机导致数据库压力突增。核心应对思路是过期时间加随机值避免同一时刻过期缓存集群做高可用数据库侧做限流和降级。如果题目问“缓存穿透怎么办”我会按“问题现象 → 原因 → 方案 → 方案对比”来答。比如布隆过滤器能拦截不存在的 key但它有误判率而且不能删除单个 key如果业务上需要删除某个不存在的 key 后立即可见空值缓存可能更友好。这样答出来就有差异化而不是背一段网上的标准答案。3.2 缓存和数据库一致性为什么“删缓存”比“更新缓存”更稳这是一个非常经典的主观题考察的是读写链路的并发设计能力。先把业务背景讲清楚读多写少场景下我们通常先读缓存未命中再读数据库并回填缓存写场景下要保证缓存和数据库最终一致。常见的方案组合有四种先更新数据库再更新缓存。先更新缓存再更新数据库。先删除缓存再更新数据库。先更新数据库再删除缓存。对比下来“先更新数据库再删除缓存”是被讨论得最多、也相对稳妥的方案。原因是删除缓存是幂等操作即使缓存不存在删除也不会报错而更新缓存是一个写操作并发情况下容易把旧值写回去覆盖新值。删除缓存如果失败可以配合重试或者订阅数据库变更日志来异步删除。但“先更新数据库再删除缓存”并不是没有坑。经典问题是更新数据库还没提交时另一个请求读到了旧值回填缓存随后删除缓存动作执行晚了脏数据留在了缓存里。严格方案常用延迟双删即先删除缓存再更新数据库延迟一小段时间后再删除一次缓存目的是把并发窗口期内可能回填的旧值清掉。不过延迟双删也不是银弹它只是缩小了不一致窗口。真正要做到强一致得靠分布式锁、版本号或同步双写等手段。主观题答案里不需要追求面面俱到但要把“每个方案的缺点”说出来然后用“在当前业务场景下如何取舍”做结尾这是常见的加分点。比如一个内部管理后台缓存不一致能接受 10 秒那“先更新数据库再删除缓存”加上一个简单的重试机制就足够了。3.3 一个“缓存更新”答题模板如果题目是“设计一个读多写少的服务端缓存更新方案”可以这样组织答案明确约束允许短时间不一致但最终一致可接受读多写少目标缓存命中率 90% 以上。读路径读缓存命中则返回未命中则查数据库回填缓存并设置过期时间。写路径先更新数据库再删除缓存删除失败时走重试队列。兜底对热点 key 做逻辑过期防止击穿所有 key 过期时间加随机偏移防止雪崩数据库入口做限流防止流量打到库上。监控记录缓存命中率、回源 QPS、缓存删除失败次数超过阈值时告警。这个模板覆盖了“正常流程 异常兜底 可观测性”比直接回答“用 Redis 做缓存”要完整得多。在答题时还能顺带提一句如果 Redis 内存足够大可以把空值也缓存起来既能防止穿透又能减少数据库压力。这会让答案显得更有实战细节。4. 消息队列与异步化主观题4.1 为什么服务端主观题离不开消息队列主观题里经常出现“订单创建后需要发送短信、通知用户积分、更新搜索索引你怎么设计”这类描述。如果全部同步调用主链路会被拉得很长任何一个下游抖动都会拖垮订单接口。消息队列在这里的三大价值是异步化、解耦、削峰。答题时最好从这三个价值分别展开异步化上游只发消息不等待下游处理主链路耗时从几百毫秒降到几十毫秒。解耦下游系统变更不影响上游只要消息协议不变上下游可以独立演进。削峰秒杀期间流量集中消息队列承担缓冲让下游按自己的消费能力处理避免被瞬时流量冲垮。这里还要补一个对比为什么不能只用线程池做异步线程池异步适合单机内部消息队列则能跨进程、跨集群传递并且自带持久化、重试和消息回溯能力。但引入消息队列也带来一致性、重复消费、消息积压等问题。主观题高分会答出“引入成本”的权衡而不是只吹消息队列有多好。4.2 消息可靠性不丢不重怎么答典型问法是“如何保证消息不丢失、不重复消费”。第一步要把消息生命周期拆成三段生产端、Broker 端、消费端。生产端发送方需要等待 Broker 的确认确认成功才算发送成功不能确认就重试但要设置重试上限和失败记录防止无限重试放大压力。Broker 端消息要落盘不能只存在内存里。写入磁盘并确认副本同步完成后才返回生产端 ack。这里可以提一嘴刷盘策略和副本同步策略体现你对底层机制的熟悉程度。消费端消费成功后提交位点。如果消费成功但提交失败重启后可能重复消费如果先提交再处理处理失败时会丢消息。所以常见做法是“本地业务处理成功后再提交位点”然后依赖业务幂等来兜底重复消息。“不重复”的解法核心是幂等。可以从三个层面说数据库唯一键约束、Redis 状态位、业务逻辑自身幂等。比如订单消息里带一个messageId或业务唯一标识消费前先查一下订单状态已处理过就直接返回不重复执行后续业务逻辑。4.3 消息积压问题的答题路径“消费端积压了几百万条消息怎么处理”是主观题里的高频题。最常见、也最稳妥的答题结构是先判断积压是否还在增长。如果生产端消息还在猛增优先摘流量、停非核心消费者或者快速扩容消费者实例数。再扩容消费能力。最简单的是临时增加消费实例同时增加分区数或队列数。如果消费者自身有瓶颈比如慢 SQL要先优化消费者逻辑否则扩多少实例都没用。最后处理旧消息。如果消费速度跟不上可以临时写一个“跳过非核心逻辑”的消费者先把消息快速消费掉之后异步重放核心部分。这道题也是典型的主观题没有标准答案重点考察“应急意识”和“扩容思路”。答题时如果提到“扩容要连队列数和消费者数一起扩”会显得很专业。因为在 RabbitMQ 这类队列模型里单纯扩充消费者不一定会提升总消费速度如果队列本身成了瓶颈扩消费者意义不大。5. 分布式系统与一致性主观题5.1 分布式锁用 Redis 还是 ZooKeeper主观题里常出现“多个服务同时处理同一用户的订单如何保证不重复扣款”。答案通常会围绕分布式锁展开。一般答 Redis 分布式锁的思路是用SET key value NX EX timeout加锁value 设置为当前线程唯一标识释放锁时用 Lua 脚本先比较 value 再删除防止误删别人持有的锁同时设置合理的过期时间防止持有锁的服务宕机后形成死锁。如果只答到这里算及格。想拿高分需要说出 Redis 锁的局限主从切换时锁可能丢失因为锁数据还没来得及同步到从节点主节点就挂了。所以严格场景下要考虑 Redlock或者直接用 ZooKeeper 的临时顺序节点实现锁。ZooKeeper 的好处是临时节点会随会话异常自动删除天然有“租约”概念代价是性能和稳定性依赖 ZK 集群复杂度更高。答题时不要试图说“哪个一定最好”而是结合业务场景下结论。比如高并发秒杀场景可以接受极端情况下锁失效那么 Redis 就够了如果是支付、扣款这类强一致场景建议 ZK 或数据库乐观锁来兜底。这样答出来的“选型逻辑”才是主观题最想看到的。如果写释放锁的 Lua 脚本可以简短写出关键代码if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end这段脚本解决的是“误删别人的锁”问题因为它会先校验当前锁的持有者标识再执行删除操作。答分布式中提到这个细节能明显增加可信度。5.2 分布式事务从 2PC 到 TCC、本地消息表主观题里问分布式事务时常给的场景是跨服务转账账户 A 扣钱账户 B 加钱两个服务在两个数据库里如何保证事务答题建议先走一张对比表把主流方案摆出来方案核心思路优点缺点2PC两阶段提交协调者先询问再提交强一致思路简单同步阻塞、协调者单点、性能差TCCTry-Confirm-Cancel业务补偿灵活性高性能较好侵入业务实现复杂本地消息表借助本地事务发消息消费方做幂等简单可靠适合最终一致只能保证最终一致需处理消息重试Saga长事务拆分失败时反向补偿能处理长流程编码复杂补偿逻辑难设计答题时如果业务要求最终一致我通常会推荐“本地消息表 消费幂等”因为它在工程落地难度和可靠性之间比较平衡。思路是服务 A 在本地事务里同时写入业务数据和消息表然后异步发送消息给服务 B服务 B 消费成功后回执如果消息发送失败有定时任务扫描消息表重发。这套方案在不少中小团队里就是默认选项。主观题答分布式事务时最忌直接说“用 2PC”因为 2PC 在真实场景里的实现限制很多协调者容易成为单点参与者资源会一直锁定。最好先把“强一致 vs 最终一致”的业务诉求点出来再选方案这样才能体现系统设计能力。6. 网络与高可用主观题6.1 TCP 和 HTTP 在服务端视角下的高频问法主观题里经常有“从 TCP 连接角度解释为什么 HTTP/1.1 会出现队头阻塞”这类问题。答这种题需要把协议栈和业务现象打通不能只背定义。答题框架TCP 是面向字节流的可靠传输协议HTTP 是应用层协议基于 TCP 传输。HTTP/1.1 的队头阻塞本质上是同一个 TCP 连接上多个 HTTP 请求串行处理前面的请求没返回后面的请求只能等待。HTTP/2 引入多路复用解决了应用层的队头阻塞但 TCP 层的丢包重传仍然会阻塞整个连接所以还存在 TCP 层的队头阻塞。这个回答就比单纯说“HTTP/1.1 性能不好”有层次得多。如果题目问“服务端如何设计一个长连接协议”可以从这几个角度答心跳保活、消息边界设计、超时重连、会话状态管理、消息序号与 ack 机制。这些点能体现你是否真的写过网络服务而不是只背过三次握手和四次挥手。6.2 高可用设计的通用答案结构“线上服务突然被大量请求打过来你如何保证系统不挂”这类题在服务端主观题里几乎年年都有。答题时绕不开限流、熔断、降级、超时、重试、幂等这几个关键词。我建议大家用“先防住再治本”来组织。流量入口做限流按接口维度设置 QPS 上限超过部分快速失败避免拖垮全部服务。对下游依赖做超时控制所有外部调用必须有超时时间不能无限等。对不稳定依赖做熔断连续失败率超过阈值时直接短路不再请求下游。非核心功能做降级比如推荐列表挂了可以降级成按热度排序短信服务挂了可以降级成只记录日志。重试必须有限次且有退避默认重试 3 次指数退避避免重试风暴。写操作尽量幂等请求带唯一请求号服务端对重复请求去重。这个结构几乎可以套进所有高可用场景题。答题时再结合题目给出的具体业务补充细节比如秒杀场景限流阈值怎么定、降级哪些模块答案就很扎实了。6.3 如何用“一页纸”复习模板沉淀知识复盘这份旧题集之后我养成了一个习惯把每个主题压缩到一页纸的模板里格式如下问题定义题目里到底要解决什么。核心概念涉及哪些协议、组件、原理。标准方案两到三个可落地方案。方案对比性能、一致性、复杂度上的差异。选型建议结合某个具体业务场景给出最终结论。针对高可用题我的一页纸会写限流是入口控制熔断是调用控制降级是兜底控制幂等是数据控制。四者不是替代关系而是组合关系。这个总结在面试时能快速帮你定位思路也能在笔试时帮你稳住论述结构。7. 主观题答题方法论与常见扣分点7.1 拿到题后先在草稿纸上做什么我见过太多人失分不是不会写而是上来就开始答写到一半才发现偏题了。我的建议是至少留出 3 到 5 分钟做“切题”圈出题眼把题面里的关键词圈出来比如“高并发”“如何保证”“故障场景”“设计思路”。列出知识关键词把你能想到的术语列出来比如“线程池、队列、限流、熔断、幂等”。构建框架按“现象 - 原因 - 方案 - 取舍”排好顺序。补充细节给每个方案补一个例子、参数或流程说明。总结落点用一句话说明在什么业务约束下选什么方案。这个过程 5 分钟足够但能让答案质量上一个台阶。很多人怕时间不够直接动笔结果写到一半发现思路错了反而更浪费时间。7.2 阅卷人到底怎么给分根据我参与过的面试流程经验主观题评分一般分三档档次表现分数反馈高有结构、有对比、有场景化取舍接近满分中结论正确但缺少推导和对比中等偏上低只写一堆概念没有逻辑链偏低所以给分关键不在“有答案”而在“有推导过程”。比如答“为什么用 Redis 做分布式锁”如果加了“原子指令、过期时间、释放锁的 Lua 脚本、主从问题”四个支撑点就能从“我知道 Redis 能做锁”变成“我会设计一把可靠的分布式锁”。7.3 常见扣分点速查表把我在复盘和辅导中常见的扣分情况整理成一张表扣分点例子正确做法答案没有结构直接罗列技术名词先写结论再给论据只谈优点不谈缺点“用 Redis 做缓存很快”补充一致性、雪崩等代价方案没有场景“用 2PC 保证强一致”先分析业务要求再选方案没有兜底设计只讲正常流程加失败重试、超时、降级、监控术语不一致一会说队列一会说缓冲统一使用规范术语面试和笔试的主观题最终考察的都是“工程思维”。技术名词可以忘但不能没有思考问题的方式。8. 从一份旧题集能带走的长期价值最后聊一点复盘之外的个人体会。这份题集虽然是 2018 年的但里面的知识点并没有过时。我后来在工作中真正遇到缓存穿透、消息积压、线程池打满时脑海浮现的恰恰是当初复习主观题时练过的答题框架。区别在于以前是为了得分现在是为了解决问题。所以如果你正打算参加服务端岗位的笔试不要只盯着“背答案”。找一个空闲的下午把每个主题当成真实线上问题去推演先自己写出答案再去对照资料补充。你会发现主观题练的不只是考试能力更是你在生产环境里做技术判断的能力。这才是这份旧题集真正的价值所在。