面试场上聊“请求链路”,一开口就能看出你是自己写过核心流程,还是只在组里跟着联调过。尤其是54人这种规模的项目,服务拆了十几个,链路拉得很长。有人能从浏览器一路讲到数据库,也有人一到中间某两段就卡壳——不是不知道,是细节经不起追问。
我自己带过、也参与过这种规模的共建项目,面过不少人。标题里说的“最容易被问住的两段”,其实非常集中:一段是从网关到目标服务的路由和鉴权过程,另一段是从业务服务到数据库/缓存的调用过程。这两段卡住的人特别多,原因也特别真实:平时大家都在这两段上“能用就行”,没有真正把每一跳协议、缓存策略、连接池行为、异常分支捋清楚。
这篇文章就把这两段链路完整拆开。从整体链路长什么样,到每一跳做了什么、可能挂在哪,再到面试官连环追问怎么答,最后附上排查工具和避坑清单。适合正在准备后端岗位面试的同学,也适合在多人协作项目里做事但没理清楚全貌的开发者。
1. 54 人共创项目里的请求链路,到底长什么样
1.1 先画一张完整的链路地图
54个人共同开发一个项目,通常意味着代码仓库不止一个、服务不止一个。典型的架构是一个前端门户,加上网关层,后面挂若干个业务域服务,再往下是公共中间件:注册中心、配置中心、缓存集群、消息队列、数据库集群。
一次完整请求从浏览器出发,大致经历这么几个大跳:
- 浏览器发起HTTP请求,经过DNS解析、CDN、负载均衡,先到Nginx或云上SLB
- 再由Nginx转发到统一接入网关(Spring Cloud Gateway、Kong、APISIX这类)
- 网关做路由匹配、全局鉴权、限流,把请求转发到对应的业务服务实例
- 业务服务在Controller层接收,参数校验后调用Service层,期间可能查Redis、调其他服务(通过OpenFeign、Dubbo、gRPC)
- Service层把结果拼装后返回,期间可能触发数据库读写
- 响应沿着原路返回,同时链路追踪系统把Trace ID、Span记录下来
这张地图好画,但面试官很少让你画“大动脉”,他们更爱挑毛细血管问。
1.2 为什么偏偏是那两段最容易被问住
先说结论:网关到服务这一段容易被问住,是因为它横跨了网络、网关组件、注册中心、负载均衡、鉴权方案五个知识域,每个人平时只接触其中一角。比如有人天天配路由,却不知道网关怎么选实例、怎么处理超时重试。
服务到数据库这一段容易被问住,是因为它叠加了框架机制、事务语义、连接池、缓存一致性、SQL执行计划这些深层概念,光靠CRUD经验根本覆盖不了。很多人会写@Transactional,但被问到传播行为就慌了;会用Redis缓存,但被问缓存和数据库的一致性问题就答不到点子上。
这两段都有一个共同特点:看起来简单,追问起来全是不确定。一旦你说错一个细节,面试官很容易顺着往下深挖,然后你就发现漏洞越来越多。
2. 第一段高危区:从网关到目标服务的路由与鉴权链路
2.1 请求到达服务实例前,网关都做了哪些事
先从入口说起。请求到达网关时,网关第一件事不是找服务,而是完成路由匹配。路由匹配的本质是把请求URL、Method、Header等信息,和路由规则做比对,找到对应的服务ID。比如说/order/**匹配订单服务,/user/**匹配用户服务。
这一步看起来是配置活,但面试官真正问的是:**匹配到服务ID之后,网关怎么找到真正处理请求的那台实例?**这就要说到注册中心和负载均衡了。
网关收到服务ID后,并不会自己去数据库查地址,而是从注册中心(Nacos、Eureka、Consul)拉取服务实例列表。这个过程通常有两层缓存:网关本地缓存一份服务列表,同时监听注册中心变更推送。本地缓存有30秒左右的过期时间,所以发布上下线时不是立刻生效。
获取实例列表后,网关会按负载均衡策略选一个实例。默认是轮询,但实际生产环境中更多用加权随机、最小连接数。还有一个大多数人忽略的点:**网关转发时,是用HTTP还是RPC?**Spring Cloud Gateway用的是Netty发起异步HTTP请求,走的是http://ip:port这种形式。所以如果服务实例没有直接暴露给网关网络,就会出现“路由规则没问题,但转发失败”的现象。
这一段我面过很多人,最常听到的回答是“通过Feign调用”。但网关这一层,Feign并不直接参与。Feign是服务到服务的调用方式,网关依赖的是lb://协议和ReactiveLoadBalancer。面试官就等着这句话,你多说一个lb://,至少证明你清楚网关和服务间是两条不同的调用语义。
2.2 JWT 鉴权与用户上下文传递的细节点
第二个高频问点是鉴权。现在的项目基本都是无状态JWT方案。客户端登录后拿到Token,后续请求放在Header的Authorization里。网关侧通过GlobalFilter拦截请求,校验JWT签名和有效期。
面试官一般会追问三个层次的问题:
第一层,**JWT签名用的什么算法?密钥存在哪?**你要能说出HS256对称签名和RS256非对称签名的区别。对称签名所有服务共用同一个密钥,泄漏了就能伪造;非对称签名用私钥签发、公钥验证,更适合多服务场景。密钥一般放在配置中心,而不是写在代码里。
第二层,**校验通过后,用户信息怎么传给下游服务?**现在常见的做法是网关解析JWT后,把userId、userName、角色信息放进Header,比如X-User-Id、X-User-Name。这里有个非常细的坑:必须在下游网关入口过滤掉客户端自带的这些Header,否则用户自己伪造一个X-User-Id就能越权。很多项目就是漏了这一步,导致数据越权漏洞。
第三层,**JWT过期后怎么处理?**这就要讲到Access Token和Refresh Token双Token机制。Access Token短时效,Refresh Token长时效,Access过期后用Refresh换新。面试官喜欢问的是刷新接口怎么防并发,标准答案是加分布式锁或者用版本号控制。
实操心得:鉴权Filter里最容易忽略的是白名单机制。登录接口、验证码接口、回调接口必须放行,但放行列表不能写在Filter的if判断里散着加,建议放到配置中心动态管理,否则每次上线加白名单都要重启网关。
2.3 超时、重试、限流这三个兄弟很容易挂
网关层最容易出问题的其实不是鉴权,而是超时、重试、限流三者叠加。超时设置太短,下游慢请求直接断开;超时设置太长,网关线程被拖死;重试开得太激进,下游偶发故障直接被流量打爆;限流没做,热点事件一来全服务雪崩。
先说超时。网关转发到服务的连接超时、读取超时通常要分别设置。连接超时一般500ms到1s,读取超时按业务类型区分:查询类接口可以给2到3秒,写操作或者异步任务接口可以放宽到5到10秒。设置的原则是比下游服务的RT阈值高一点,但不要超过网关整体的兜底时间。
再说重试。Spring Cloud Gateway默认不重试,但你一旦开了RetryFilter,必须想清楚重试是否幂等。查询接口重试没有问题,但订单创建、转账扣款这种写接口,重试可能导致重复下单。所以重试策略通常只对GET请求开启,写接口最多做一次“连接失败重试”,响应超时绝不重试。
最后说限流。网关限流一般用Redis +令牌桶或者Sentinel。面试官问限流,通常想知道你选择的限流维度。按IP限流太粗,按用户ID限流更合理,但要注意用户ID可能不存在(未登录场景),所以要降级到IP维度。
3. 第二段高危区:从业务服务到数据库与缓存的调用链路
3.1 Controller到Mapper之间的“隐形”处理
很多人以为服务内部链路就是Controller调Service、Service调Mapper。但面试官真正想听的是每一层之间那些“隐形”的处理过程。
Controller层除了参数校验,还有两件事:一是把HttpServletRequest中的用户身份信息解析出来,绑定到当前线程上下文(比如UserContext),这样Service层就不用每次从Header里取;二是做参数转换,把DTO转成Service层的领域对象。这里有个常见问题:很多人不区分DTO、VO、Entity,一套模型用到底,导致Service层和数据库表结构强耦合,后面分库分表、字段改名都是灾难。
Service层核心是业务编排。它可能会做几件顺序不定的事:查Redis缓存、调其他服务的Feign接口、写本地事务、发MQ消息。这里面试官最爱的埋点是事务边界。
我举个例子。用户下单接口,Service层方法标了@Transactional,里面先调用库存服务扣库存(Feign远程调用),再写本地订单表。如果扣库存成功、写订单表失败,事务回滚,但库存已经扣了,这就产生了分布式事务问题。面试官会顺着问你怎么办,常见的答案有:靠MQ最终一致性、本地消息表、Seata分布式事务。你至少要能说出一个靠谱方案,并且讲清楚选择理由,而不是笼统回答“用分布式事务”。
实操心得:多人协作项目里,
@Transactional经常被乱加。有的同事给只读查询方法也加事务,有的同事在事务里发短信、调外部接口,这些都是线上事故的种子。我自己的规范是:事务只覆盖本地数据库写操作,远程调用、MQ发送一律放在事务提交后,通过TransactionSynchronizationManager.registerSynchronization回调去做。
3.2 数据库连接池和事务传播行为,被问住的重灾区
数据库连接池这一块,95%的简历都会写MySQL,但很多人不知道项目里其实用的HikariCP连接池。面试官问一个简单问题就能暴露深浅:连接池最大连接数设置成多少?为什么?
这里不能背公式。要回答这个问题,得知道连接池大小和并发数、单请求DB操作耗时之间的关系。参考公式是:连接池大小 = 峰值QPS × 单个请求平均DB耗时(秒)。比如说峰值QPS是1000,单请求平均DB耗时50ms,那就需要1000×0.05=50个连接。还要留余量,一般乘1.5到2倍,设成80到100。
事务传播行为更是重灾区。REQUIRED、REQUIRES_NEW、NESTED,很多人背得出名字,但不知道什么时候用。我说一个实际场景:批量导入数据,每条数据独立处理,一条失败不能影响其他条。如果主方法加了@Transactional,内部循环调用子方法,子方法抛异常会把整个事务回滚。解决办法就是给子方法加@Transactional(propagation = Propagation.REQUIRES_NEW),让它独立提交。但代价是两条事务之间失去原子性,你要自己处理失败后的补偿逻辑。
还有NESTED和REQUIRES_NEW的区别也常被问。NESTED用的是保存点(Savepoint),回滚到保存点而不是整个事务;REQUIRES_NEW是挂起当前事务、开一个全新事务,两者在“是否与外部事务共享锁资源”上有本质差异。
3.3 缓存与数据库双写,链路里最容易被忽视的隐患
服务查询数据时,常见动作顺序是:先查Redis,命中直接返回;没命中则查数据库,回填缓存。这本身没有大问题,但缓存更新策略才是坑。
很多人用的是“先更新数据库,再删除缓存”方案。面试官会追问:为什么是删缓存而不是更新缓存?答案是避免并发下两个线程交替写库把缓存搞脏。删缓存简单粗暴,即使出问题最多是缓存未命中、回源数据库。
但“先更新数据库,再删缓存”也有问题:如果删缓存失败,老数据还在Redis里。解决方式有多种,最靠谱的是订阅Binlog异步删除缓存,也就是Canal监听MySQL变更,删除对应缓存;或者给缓存设一个较短的过期时间作为兜底。
面试官另一个常问:**缓存穿透、缓存击穿、缓存雪崩区别是什么?分别怎么应对?**这里要说出三个名字加上对应的方案。穿透指查一个不存在的数据,每次都要打到DB,用布隆过滤器拦截;击穿指某个热点Key过期瞬间大流量打向DB,用互斥锁或者逻辑过期;雪崩指大量Key同时过期或Redis宕机,用过期时间加随机值、本地缓存兜底、Redis高可用。
这一段其实非常能体现一个人的真实经验。如果你在项目里真的遇到过缓存穿透,你能说出当时压测QPS是多少、DB线程池被打满的现象、后来用布隆过滤器后命中率变化。这些都是伪造不出来的细节。
3.4 慢SQL是怎么“藏”在链路末端的
最后一环是数据库执行。面试官习惯问:链路某个接口突然变慢,你怎么快速定位?如果你一上来就说看监控,那等于没说;标准路径是先看链路追踪里哪一段耗时飙升,如果卡在SQL执行,再去拿慢查询日志和EXPLAIN执行计划。
常见慢SQL原因就那几类:没走索引、索引失效、深分页、大表join、行锁等待。
深分页是多人项目里很常见的坑。limit 100000, 20会先把前面10万行取出来再丢弃,非常耗时。优化方式是改成游标分页,用上一页最后一条ID做条件,比如where id > 100000 limit 20。面试官喜欢追问:游标分页怎么解决跳页问题?你可以回答跳页用between and,或配合ES用search_after。
另外,行锁等待这个问题,很多人在项目里踩过但说不清楚原理。比如批量更新同一行数据,两个事务互相等待,就会出现Lock wait timeout exceeded。面试官更希望你从源头回答:更新条件尽量走索引、where条件精确到行、事务范围控制小、批量操作错峰。
4. 面试连环追问实录与链路排查工具清单
4.1 三个高频追问,这样答才像真的做过
我模拟一下面试官在这两段链路最可能追的三个问题,以及我觉得还不错的回答方向:
第一个问题:“你说网关做了鉴权,那服务内部会不会还要校验用户权限?”比较完整的回答是:网关只做身份认证(你是谁),权限校验(你能干什么)放在服务内部,通过自定义注解加拦截器实现,在Service方法上标@RequiresPermission("order:create"),方法执行前把当前用户角色从Redis里取出来比对。网关层做接口级权限,服务层做数据级权限,两层职责不同,不能省掉其中一层。
第二个问题:“某一个接口突然从50ms变成5秒,你作为项目成员怎么排查?”比较好的回答有顺序:先看告警和链路追踪,确定是哪个环节慢;如果是Redis慢,看bigkey和慢命令;如果是DB慢,看慢查询日志和锁等待;如果是Feign调用慢,看被调服务RT和线程池队列。每一步都要有现象依据,不能瞎猜。
第三个问题:“如果你加入这个项目,第一件事做什么?”这个题实际上是考察你有没有全局视野。比较好的回答是:先要一份服务拓扑图和接口文档,梳理核心链路;再看链路追踪系统里的慢请求Top100,挑出自己负责服务的TOP5逐一排查;然后了解发布流程和配置中心分支管理规范。这个回答既展示了技术能力,也展示了协作意识。
4.2 实际排查请求链路问题的工具箱
面试官问“请求链路怎么走”,潜台词其实是“出问题时你怎么知道走到哪了”。所以你要能说出可落地的排查工具。
第一是链路追踪系统。54人项目基本都会上SkyWalking、Zipkin或Jaeger。核心概念是Trace和Span。我排查问题时,先看Trace里每个Span的耗时,哪个Span耗时异常就顺着这个Span翻日志。
第二是日志平台。日志必须带上Trace ID,这样从网关到服务到数据库全链路日志可以串起来。排查步骤很明确:拿到一个出错的Trace ID,在日志平台搜,按照时间线把每一条日志拼出来。
第三是数据库和中间件监控。MySQL慢查询、Redis slowlog、MQ消费积压数、连接池活跃数,都要有监控大盘。这里尤其推荐一下Arthas,线上排查CPU飙高、线程阻塞、方法耗时都靠它,trace命令可以直接看方法内的每个子调用耗时。
4.3 请求链路常见问题速查表
整理一张问题速查表,面试前和实际排查时都能用到:
| 症状 | 可能原因 | 排查方法 | 关键命令/工具 |
|---|---|---|---|
| 网关转发超时 | 下游服务RT变长 / 连接池被打满 | 链路追踪看下游Span耗时 | SkyWalking / Arthas |
| 路由404 | 注册中心实例下线 / 路由规则未更新 | 查Nacos服务列表和网关路由表 | Nacos控制台 |
| 鉴权偶尔失效 | JWT密钥轮换导致旧Token异常 | 查网关日志和密钥版本 | 日志平台 |
| 接口偶尔慢 | 缓存穿透/热点Key过期 | 看Redis命中率和慢命令 | Redis slowlog |
| 数据库连接耗尽 | 慢SQL持有连接过久 | 查连接池活跃数和慢日志 | show processlist |
| 事务回滚失效 | 异常被catch / 传播行为配置错误 | 加日志查看异常抛出点 | 日志平台 |
| Feign调用超时 | 被调服务线程池排队 | 查被调服务线程池活跃数 | Arthas dashboard |
| 数据不一致 | 缓存删除失败 / 双写竞态 | 查Binlog消费和缓存过期时间 | Canal监控 |
4.4 给新入54人协作项目的同学几条避坑清单
最后按我的经验,给刚进入这种大型共创项目、或者正准备面试讲这种项目的同学几条实在建议:
第一,不要把所有的请求链路都背成八股。面试官想听的是你真实经历的链路。你可以从自己参与的那个需求开始讲:用户点了哪个按钮、请求怎么进来、你改了哪一行代码、出过什么问题。哪怕简单,也比背得天花乱坠真实得多。
第二,一定要去线上看一次真实的链路Trace。很多人只在本地跑通逻辑,没看过生产环境的链路图。我强烈建议花半小时,在链路追踪系统里找一个真实请求,展开看每一跳,再和代码对照,你会发现自己对框架的理解上了一个台阶。
第三,摸清两段链路的默认配置。网关超时参数、连接池大小、Redis过期策略、事务超时时间,这些默认值项目里都有人改过。你入职第一周应该把这几个配置翻一遍,问明白为什么这么设置——这比多写200行代码有价值得多。
第四,养成“出问题先画链路,再动手改”的习惯。多人项目最大的坑是一上来就改代码。你最少要把请求走了哪几个组件、每个组件日志的TraceID长什么样搞清楚,再谈修复。否则很可能你改了A服务,问题出在B服务的缓存,白忙一场。
我个人在这类项目里最大的体会是:请求链路不是背出来的,是一次次查问题查出来的。你多陪线上问题熬几次夜,那些曾经背不出的细节,自然就刻在脑子里了。下次面试官再问“请求链路怎么走”,你就直接从你后端的代码出发,一路讲到数据库的行锁,讲的过程中还能顺带吐槽一句当时是怎么用Arthas定位到那个for update的——这种东西,假的真不了,真的假不了。