跳转至

13. 面试真题与自测

十三、面试真题与自测

这一章只做两件事:

  1. 保留接近真实面试现场的问法;
  2. 告诉你回答时必须覆盖哪些关键点。

原理、代码和完整案例仍放在前面的教学正文中。建议先遮住“回答检查点”,用 1~3 分钟口述,再逐项核对。

怎样使用这一章

一道技术题至少练三遍:

  • 第一遍:一句话下结论。 先让面试官知道你会什么。
  • 第二遍:讲清因果链。 从现象、原理讲到方案和边界。
  • 第三遍:落到实现。 能说出数据结构、关键命令、代码位置、指标或验证方法。

回答项目题时,只能使用自己真正做过的事实。文中的项目题给的是组织方式,不是可直接冒充的经历。


一、Redis、MySQL 与消息队列

Q1:为什么 Redis 去重和业务唯一键要同时存在?

真题: 创建订单或处理支付回调时,已经用 Redis SETNX 去重了,为什么数据库还要建唯一索引?

回答检查点:

  • 重复请求可能来自客户端重试、网关超时、MQ 重投和消费者重启。
  • Redis 负责快速拦截重复流量,Key 会过期,也可能在故障切换时丢失。
  • 数据库唯一键或幂等记录负责最终正确性。
  • 唯一键冲突后应返回第一次执行结果,不能再次产生副作用。

继续追问: 第一次请求仍在处理中,第二次请求应该返回什么?幂等记录和业务写入怎样放进同一个事务?

Q2:分布式锁怎么实现?SET NX 有什么问题?

真题: Redis 分布式锁怎样加锁和释放?为什么不能直接 DEL?锁过期后旧线程恢复执行怎么办?

回答检查点:

  • 使用一条原子命令完成 SET key token NX PX ttl
  • Token 必须属于当前持有者,释放时用 Lua 完成比较和删除。
  • TTL 防止永久锁,无法阻止旧持有者在过期后继续写。
  • 核心资源需要 fencing token、数据库版本号或条件更新拒绝迟到写。

继续追问: 哪些场景不适合 Redis 锁?续期线程本身暂停会发生什么?

Q3:缓存穿透、击穿和雪崩分别是什么?

真题: 缓存突然失效后数据库被打满,你怎样判断是穿透、击穿还是雪崩?

回答检查点:

  • 穿透:持续查询不存在的数据;使用参数校验、布隆过滤器或短 TTL 空值缓存。
  • 击穿:单个热点 Key 过期;使用 singleflight、互斥回源或提前刷新。
  • 雪崩:大量 Key 同时过期或 Redis 整体不可用;使用 TTL 抖动、限流、降级和多级缓存。
  • 必须结合命中率、回源 QPS、数据库连接等待和热点分布判断。

继续追问: 空值缓存会带来什么一致性问题?Redis 故障时能否允许全部请求回源?

Q4:热 Key 和大 Key 怎么处理?

真题: Redis 集群已经扩容,为什么某个节点仍然被打满?大 Key 删除为什么会引起延迟抖动?

回答检查点:

  • 热 Key 是访问集中问题,单个 Key 仍固定落在一个分片。
  • 大 Key 是对象体积问题,会放大序列化、网络、复制、持久化和删除成本。
  • 热 Key 可使用本地缓存、读副本、多副本 Key、请求合并或限流。
  • 大 Key 应拆分集合,用 SCAN 渐进遍历、UNLINK 异步释放,并限制单 Key 大小。

继续追问: 怎样在线发现热 Key?多副本 Key 如何处理更新一致性?

学习入口: 缓存专题

Q5:InnoDB 的索引结构是什么?二级索引为什么会回表?

真题: 聚簇索引和二级索引的叶子节点分别存什么?覆盖索引为什么更快?

回答检查点:

  • InnoDB 使用 B+ 树;主键索引叶子节点保存完整行。
  • 二级索引叶子节点保存索引列和主键值。
  • 通过二级索引得到主键后,再访问聚簇索引取其他列,称为回表。
  • 查询字段全部位于同一索引时形成覆盖索引,可以减少随机 I/O。

继续追问: 为什么不在二级索引叶子节点保存完整行?主键过大会有什么影响?

Q6:联合索引 (a,b,c)WHERE b=? AND c=? 能走吗?

真题: 什么是最左前缀?为什么范围条件之后的字段通常不能继续用于索引定位?

回答检查点:

  • 联合索引按 a → b → c 的字典序排列。
  • 缺少 a 时,b,c 在整棵树中并非连续有序,通常无法高效定位。
  • a=? AND b>? 进入 b 的范围后,同一范围内的 c 不再形成一个连续定位区间。
  • 优化器可能使用 Skip Scan 或 ICP,但不能代替按真实查询设计索引。

继续追问: a=? AND c=? 怎样执行?索引列顺序应该依据区分度还是查询模式?

Q7:为什么深分页不用 LIMIT offset,size

真题: LIMIT 1000000,20 为什么慢?怎样改成游标分页?

回答检查点:

  • 数据库仍需扫描、排序并丢弃 offset 之前的记录。
  • 查询宽列时,大量回表会进一步放大成本。
  • 稳定排序场景可用上一页末尾的 (created_at,id) 继续做范围扫描。
  • 游标分页性能稳定,但不适合直接跳到任意页。

继续追问: 只有 created_at 能否作为游标?翻页过程中插入新数据会怎样?

Q8:慢 SQL 怎么排查?

真题: 一个接口 P99 突然升高,定位到 MySQL 后,你按什么顺序排查?

回答检查点:

  • 先用慢日志、APM 和指标确认具体 SQL、频率和影响范围。
  • EXPLAIN ANALYZE 看真实扫描行数、索引、回表、排序和临时表。
  • 检查隐式类型转换、函数包裹索引列、深分页、返回列过多和统计信息失真。
  • 修改后要用接近生产的数据量压测,并通过灰度观察 P95/P99 和数据库负载。

继续追问: Using filesort 一定需要优化吗?索引越多为什么写入越慢?

Q9:乐观锁和悲观锁怎么选?

真题: 库存扣减如何用乐观锁实现?SELECT ... FOR UPDATE 锁的是行还是表?

回答检查点:

  • 乐观锁常用版本号或条件更新,通过影响行数判断成功、冲突或库存不足。
  • 它适合冲突较少的场景;高冲突下要限制重试,避免重试风暴。
  • FOR UPDATE 的实际锁范围取决于索引、查询条件和隔离级别。
  • 缺少合适索引可能扩大记录锁、间隙锁或范围锁的影响。

继续追问: 外部 RPC 能不能放在锁事务里?库存扣减为何常用原子条件更新?

学习入口: Go 后端工程:MySQL

Q10:Kafka、RabbitMQ 和同步 RPC 怎么选?

真题: 权限变更通知多个下游,为什么更适合 Kafka,而部门存在性校验更适合同步 RPC?

回答检查点:

  • 当前请求必须立即得到结果时使用同步 HTTP/gRPC。
  • Kafka 适合高吞吐事件流、长期保留、回放和分区内有序。
  • RabbitMQ 适合任务分发、复杂路由和低延迟确认。
  • 选择依据是时效要求、吞吐、回放、顺序、消费者数量和一致性模型。

继续追问: MQ 是否能让业务执行得更快?分区数量为什么决定消费并行上限?

Q11:消息怎样防丢、重复和乱序?

真题: 数据库已经提交,但消息发送失败怎么办?消费者执行成功后、提交 Offset 前宕机怎么办?

回答检查点:

  • 业务数据和 outbox 事件在同一个本地事务提交,Relay 负责后续发送。
  • 至少一次投递允许重复,消费者必须使用 event_id 或业务唯一键幂等。
  • 同一业务对象使用稳定 Partition Key,保证分区内顺序。
  • 事件携带版本号,消费者拒绝迟到的旧版本;对账任务修复尾部不一致。

继续追问: Outbox Relay 在发送成功、标记完成前宕机会怎样?Offset 为什么不是业务事实?

学习入口: 消息队列专题


二、Java、Spring、网络与并发

本节题目以 JVM、Spring 与通用网络机制为背景,覆盖简历中出现过 Java 技术栈或需要跨语言对比的场景;每题对应的完整原理见 后端基础:JVM、Spring、网络与并发。Go 语言自身的高频题(GMP 调度、channel、defer/panic/recover 等)见本节末尾 Q20-Q22。

Q12:进程和线程有什么区别?Java 线程和 Linux 线程是什么关系?

回答检查点:

  • 进程是资源隔离和分配单位,线程是 CPU 调度与执行单位。
  • 同一进程内线程共享堆、文件描述符等资源,各自拥有栈和程序计数器。
  • 主流 HotSpot JVM 使用一对一模型,Java 平台线程通常对应一个内核线程。
  • 线程切换、阻塞和过多线程会产生调度与内存成本。

继续追问: 协程或虚拟线程解决了什么问题?CPU 密集任务能否通过无限加线程提速?

学习入口: 后端基础 · 16.1 程序如何获得运行空间

Q13:Java 是编译型还是解释型?Java 进程怎样启动?

回答检查点:

  • Java 源码先由 javac 编译为字节码。
  • JVM 加载、验证、准备、解析并初始化类。
  • 字节码可由解释器执行,热点代码由 JIT 编译为机器码。
  • 启动过程还涉及堆、线程、类加载器和运行时数据区初始化。

继续追问: 类的加载和初始化有什么区别?JIT 为什么不能把所有代码一开始就编译?

学习入口: 后端基础 · 16.2 Java 代码怎样变成机器执行

Q14:Spring Boot 启动时做了什么?

回答检查点:

  • 创建并准备 ApplicationContext,加载配置和环境。
  • 扫描、注册并实例化 Bean,完成依赖注入和生命周期回调。
  • 根据条件装配加载自动配置。
  • Web 应用还会创建内嵌服务器并注册路由、过滤器等组件。

继续追问: Bean 循环依赖为何发生?后置处理器和 AOP 代理在哪个阶段介入?

学习入口: 后端基础 · 16.3 Spring 为什么能管理业务对象

Q15:@Autowired@Resource 有什么区别?反射是什么?

回答检查点:

  • @Autowired 默认按类型匹配,可结合 @Qualifier
  • @Resource 属于 Jakarta 规范,通常先按名称再按类型匹配。
  • 反射允许运行期查看类型、字段、方法和注解,并动态调用。
  • Spring 用反射和元数据完成扫描、注入、代理等能力,但生产代码仍需控制反射成本和边界。

继续追问: 多个同类型 Bean 时会怎样?反射为什么会影响可读性和静态检查?

学习入口: 后端基础 · 16.3 Spring 为什么能管理业务对象

Q16:Java SPI 和 Spring Boot Starter 的底层思路是什么?

回答检查点:

  • SPI 用接口和外部实现声明实现可插拔发现。
  • Starter 把依赖、配置属性和条件化自动配置打包,降低接入成本。
  • 核心思想都是让扩展方依赖稳定契约,让实现按需装配。
  • 要处理实现冲突、版本兼容、加载顺序和配置覆盖。

继续追问: ServiceLoader 如何发现实现?Starter 为什么通常还需要 AutoConfiguration?

学习入口: 后端基础 · 16.3 Spring 为什么能管理业务对象

Q17:线程池中怎样传递 TraceId?

回答检查点:

  • 普通 ThreadLocal 只属于当前线程,任务切换到线程池后不会自动继承。
  • 提交任务时捕获上下文,在执行前恢复,结束后必须清理。
  • 可使用任务装饰器或观测库提供的 Context Propagation。
  • 不清理会让复用线程串用上一个请求的 TraceId。

继续追问: InheritableThreadLocal 为什么不能可靠解决线程池问题?

学习入口: 后端基础 · 16.4 请求切换线程后,Trace 为什么会断

Q18:浏览器输入 URL 后发生什么?HTTP 各版本有什么差异?

回答检查点:

  • 解析 URL、查询 DNS、建立 TCP;HTTPS 还要进行 TLS 握手。
  • 请求经过负载均衡或网关进入应用,再访问缓存、数据库或下游服务。
  • HTTP/1.1 支持持久连接但存在队头阻塞;HTTP/2 使用二进制分帧和多路复用。
  • HTTP/3 基于 QUIC,减少传输层队头阻塞并改善连接建立。

继续追问: HTTP/2 为什么仍可能受到 TCP 丢包影响?TLS 证书验证什么?

学习入口: 后端基础 · 16.5 浏览器请求如何到达 Java 服务

Q19:限流、线程池和 QPS 怎么设计?

回答检查点:

  • 先测下游安全容量、单请求耗时和目标延迟,再确定并发上限。
  • 使用令牌桶控制进入速率,有界线程池和有界队列限制正在执行与等待的任务。
  • 队列过长只会把失败变成更久的超时,需要快速拒绝或降级。
  • 观察 QPS、并发数、队列等待、拒绝率、P95/P99 和下游错误率。

继续追问: QPS 相同,为什么耗时翻倍会让并发翻倍?令牌桶和漏桶有什么区别?

学习入口: 后端基础 · 16.6 QPS、并发、线程池和下游容量

Q20:Go 的 GMP 调度模型是什么?为什么不是线程一对一模型?

回答检查点:

  • G(Goroutine)是待执行的用户态任务,M(Machine)是内核线程,P(Processor)是调度上下文,持有可运行 G 的本地队列和执行 G 所需的资源。
  • 一个 M 必须先绑定 P 才能执行 G;P 的数量默认等于 GOMAXPROCS,决定了同一时刻并行执行的 G 上限。
  • G 发生系统调用阻塞时,运行时会把 M 和 P 分离,让 P 绑定到其他空闲或新建的 M 上继续调度别的 G,阻塞的 M 恢复后再尝试重新获取 P。
  • 本地队列取完后会从全局队列或其他 P 的本地队列“偷”一半任务(Work Stealing),避免个别 P 空闲而其他 P 堆积。

继续追问: GOMAXPROCS 设置过大或过小分别有什么代价?G 什么情况下会从 M 上被抢占?

学习入口: Go 基础 · 2.4 并发控制精要:G-M-P 运行时调度模型

Q21:channel 的无缓冲和有缓冲有什么区别?往已关闭的 channel 读写会怎样?

回答检查点:

  • 无缓冲 channel 的发送和接收必须同时就绪才能完成,本质上是一次同步握手;有缓冲 channel 在缓冲区未满/非空时可以异步完成发送/接收。
  • 向已关闭的 channel 发送数据会立即 panic;从已关闭的 channel 接收,会先取完缓冲区剩余数据,之后持续返回零值和 ok=false,不会阻塞也不会 panic。
  • 只应由发送方关闭 channel,且只关闭一次;重复关闭同样会 panic。
  • channel 常用于传递事件和结果,也能作为信号量、广播关闭信号(close 会唤醒所有等待的接收方),但不适合替代所有场景的锁。

继续追问: 为什么说 channel 是“用通信来共享内存”而不是“用共享内存来通信”?无缓冲 channel 能否用于超时控制?

学习入口: Go 基础 · 2.4 并发控制精要:Goroutine 调度与 happens-before

Q22:defer、panic 和 recover 的执行顺序是什么?

回答检查点:

  • 多个 defer 按后进先出(LIFO)顺序执行,即使函数提前 return 或发生 panic,已注册的 defer 仍会按顺序执行完。
  • defer 中对具名返回值的修改会影响最终返回结果,因为返回值赋值发生在 defer 执行之前,函数真正退出在 defer 执行之后。
  • panic 会中断正常执行流程,沿调用栈向上传播,依次执行每一层已注册的 deferrecover 只有在 defer 函数内部直接调用才有效,间接调用(比如在被 defer 调用的函数内部再调用一个函数去 recover)无法捕获 panic。
  • recover 之后函数会正常返回,不会自动恢复到 panic 发生的位置继续执行;跨 goroutine 的 panic 无法被其他 goroutine 的 recover 捕获,必须在该 goroutine 自己的 defer 中处理。

继续追问: 为什么 HTTP 服务的每个请求 goroutine 通常要单独 defer recoverrecover 之后怎样保留原始错误堆栈?


三、RAG 工程

Q23:知识库为什么要切块?怎样选择 Chunk 大小?

回答检查点:

  • 整篇文档向量会混合多个主题,召回定位和引用都很差。
  • Chunk 太小会切断语义,太大则主题混杂、上下文成本升高。
  • 优先按标题、段落、表格和代码结构切分,再用长度上限兜底。
  • Chunk 大小和 overlap 应通过真实 Query 集评测,不能只背固定数值。

继续追问: 父子块检索怎样兼顾定位和上下文?跨页表格如何处理?

Q24:Contextual Retrieval 是什么?每个 Chunk 加多少背景?

回答检查点:

  • 在 Chunk 前补充标题路径、文档类型、版本和简短主题摘要。
  • 它帮助孤立片段恢复文档级语境,提高 embedding 与排序质量。
  • 背景过长会压过正文,也会让多个 Chunk 变得过度相似。
  • 使用 Recall@K、MRR、引用支持率、索引成本和 P95 共同评估。

继续追问: 补充上下文应该参与引用吗?它和父文档扩展有什么区别?

Q25:关键词检索和向量检索怎样融合?RRF 怎么算?

回答检查点:

  • BM25 擅长错误码、型号和精确术语;向量检索擅长语义和同义表达。
  • 两路分别召回后,可用 RRF(d)=Σ 1/(k+rank_i(d)) 融合排名。
  • RRF 不直接比较不同检索器的原始分数,适合分数尺度不一致的场景。
  • 融合后再用 reranker 精排,并在召回前应用权限和状态过滤。

继续追问: Top-K 过大有什么代价?RRF 能否替代 reranker?

Q26:RAG 效果怎么评估?“准确率提升”应该怎么说?

回答检查点:

  • 建立按事实问答、流程问答、无答案拒答等类型分桶的黄金集。
  • 召回看 Recall@K,排序看 MRR/nDCG,生成看事实正确率、引用支持率和拒答准确率。
  • 线上还要看 P95、成本、空召回率和用户反馈。
  • 报告提升时必须说明基线、样本量、指标、变化值和代价;没有数据就明确说尚未测量。

继续追问: LLM-as-a-Judge 有哪些偏差?为什么点赞率不能代表答案正确率?

Q27:增量索引怎样避免全量重建?

回答检查点:

  • 用文档 ID、内容哈希和版本识别新增、修改与删除。
  • 只重新解析和向量化变化的 Chunk。
  • 新旧索引使用版本或别名切换,避免用户读到半更新状态。
  • 删除需要墓碑、延迟清理和引用一致性检查。

继续追问: embedding 模型升级后还能局部更新吗?索引切换期间缓存怎样处理?

Q28:HNSW、IVF 和 PQ 怎么选?

回答检查点:

  • HNSW 查询快、召回高,但内存占用较大,在线更新治理更复杂。
  • IVF 先聚类再搜索部分倒排桶,需调节探测范围与召回率。
  • PQ 通过量化压缩向量,节省内存但会损失精度。
  • 选择要结合数据量、内存、更新频率、目标召回和 P95 实测。

继续追问: 为什么不能只看 ANN 查询耗时?过滤条件会怎样影响召回?

Q29:RAG 没有召回到证据时怎样防止模型胡编?

回答检查点:

  • 检索器返回结构化证据、分数、来源和空召回状态。
  • 生成前设置最低证据门槛,证据不足时澄清、扩大检索或拒答。
  • 回答中的关键结论必须能映射到引用片段。
  • 评测集需要包含无答案问题,单独衡量拒答准确率。

继续追问: 分数阈值能否跨 Query 固定?两个来源相互冲突时怎么办?

学习入口: RAG 工程


四、Agent、Tool Calling 与 MCP

Q30:单 Agent 和多 Agent 怎么选?

回答检查点:

  • 默认先用单 Agent 或确定性工作流。
  • 工具权限、上下文、失败策略或职责出现明确边界时再拆分。
  • 多 Agent 需要稳定输入输出契约、全局状态、最大跳数、超时和 Trace。
  • 代价包括额外延迟、上下文传递损失、重复工作和编排复杂度。

继续追问: 为什么不能让多个 Agent 自由对话直到完成?哪些任务直接用工作流更可靠?

Q31:Agent 记忆系统怎么设计?时效性怎么处理?

回答检查点:

  • 区分当前任务状态、会话摘要、长期结构化记忆和向量化历史线索。
  • 每条长期记忆保存来源、作用域、时间、置信度、TTL 和审计信息。
  • 写入需要准入规则,读取时需要按时间与来源判断可信度。
  • 权限、订单、库存等强状态必须查询权威系统,不能依赖历史记忆。

继续追问: 两条记忆冲突时如何更新?摘要压缩丢失约束怎么办?

Q32:一个合格的 Tool 要定义什么?

回答检查点:

  • 输入要有窄而明确的 Schema、必填字段、枚举、长度和格式限制。
  • 输出使用稳定的结构化信封,区分错误类型和是否可重试。
  • 身份与租户信息来自可信认证上下文,不能由模型自由填写。
  • 写操作需要权限、幂等、超时、审计,必要时增加人工确认。

继续追问: 模型返回的 JSON 不合法怎么办?为什么不让模型直接传 SQL?

学习入口: Tool Calling 与 MCP

Q33:MCP 解决了什么?它没有解决什么?

回答检查点:

  • MCP 标准化客户端与工具服务之间的能力发现、Schema 和调用流程。
  • 它降低不同模型客户端重复接入工具的成本。
  • 认证、授权、租户隔离、业务幂等、审计和安全审批仍需宿主与服务端实现。
  • Tool、Resource 和 Prompt 是不同类型的能力,不应混为一个“大工具”。

继续追问: MCP Client 和 Server 如何建立连接、发现工具并回传结果?MCP 与 Skills 有什么区别?

学习入口: Tool Calling 与 MCP

Q34:Plan-and-Execute 什么时候用?

回答检查点:

  • 适合多步依赖、需要中途验证、局部重试或断点恢复的长任务。
  • Plan 应输出有限、可执行、可验证的步骤和成功条件。
  • 执行阶段保存状态,检查前置条件、权限、预算和产物质量。
  • 简单问答或单工具调用直接执行更合适。

继续追问: 规划本身错误怎么办?什么时候重新规划,什么时候直接失败?

Q35:ReAct 为什么会陷入循环?怎样止损?

回答检查点:

  • 模型可能重复选择同一工具、忽略失败结果或持续产生无效观察。
  • 对每次调用保存参数摘要、结果状态和状态版本,识别重复轨迹。
  • 设置最大步数、最大工具调用数、总时长、总成本和最大跳数。
  • 连续失败后切换方案、请求澄清、降级或转人工。

继续追问: Reflection 能否保证修正错误?怎样评估中间轨迹而不只看最终答案?

Q36:AI 生成代码怎样验证?

回答检查点:

  • AI 代码只作为候选实现,先明确验收条件和风险边界。
  • 合入前通过编译、单测、集成或契约测试、静态扫描和依赖检查。
  • 人工 Review 重点看权限、并发、数据迁移、异常路径和回滚。
  • 稳定约束应写入 ADR、Schema、类型、测试和检查规则,避免只存在于聊天历史。

继续追问: 多模型互审能否替代测试?上下文压缩后模型偏离原需求怎么恢复?

学习入口: Agent 工程开发型 Agent 教材


五、系统设计与工程追问

Q37:超时到底设置在哪一层?

真题: 请求经过网关、用户服务和部门服务,部门服务变慢时,超时从哪里开始?

回答检查点:

  • 客户端、网关、服务端和每个下游调用都要设置超时。
  • 上层是总预算,下层是子预算;下游超时必须短于上游剩余时间。
  • Deadline 通过 Context 或 RPC 元数据向下传播,取消后停止查询和后台工作。
  • 写操作超时后结果可能未知,不能不加判断地重试。

继续追问: 网关 3 秒、用户服务 2.8 秒、部门服务应该设多少?数据库超时放在哪里?

学习入口: gRPC 专题Go 后端工程

Q38:哪些调用允许重试?怎样避免写操作重复?

回答检查点:

  • 只重试瞬时故障,例如连接中断、部分 5xx、限流和明确的可重试状态。
  • 参数错误、权限错误和业务校验失败不重试。
  • 读请求通常可安全重试;写请求需要幂等键、唯一约束或状态机条件。
  • 重试要有次数、退避、抖动和总预算,避免重试风暴。

继续追问: 创建用户请求超时,客户端能否立即重发?服务端已经成功但响应丢失怎么办?

Q39:怎样设计一个高并发 RAG 服务?

回答检查点:

  • 先给出峰值 QPS、P95、文档量、索引规模、更新频率和成本目标。
  • 在线链路拆成鉴权、查询理解、缓存、混合召回、重排、模型生成和引用校验。
  • 对 embedding、rerank 和模型调用分别设置并发闸门、超时和降级。
  • 观察缓存命中、检索 P95、模型首 Token、端到端 P99、失败率和单请求成本。

继续追问: 模型并发只有 100,入口流量 1000 QPS 怎么办?哪些缓存最容易产生脏数据?

Q40:怎样根据一次请求定位跨服务故障?

回答检查点:

  • 网关生成或接收 trace_id/request_id,沿 RPC、MQ 和数据库操作传播。
  • 结构化日志至少包含服务、接口、租户、耗时、状态码、下游和错误类型。
  • 指标负责发现异常,Trace 负责定位链路,日志负责解释具体上下文。
  • 异步消息还需要 event_id、业务主键和消费位点串联。

继续追问: 只有普通日志,没有 Trace 系统时怎么排查?怎样避免日志泄露敏感字段?

Q41:讲一个真实故障应该怎么讲?

回答顺序:

  1. 现象:用户看到了什么,哪些指标异常。
  2. 复现:在什么条件下稳定出现。
  3. 证据:日志、Trace、SQL、指标或代码分别证明了什么。
  4. 根因:具体机制和触发条件。
  5. 修改:改了哪些文件或配置,为什么有效。
  6. 验证:怎样证明修复成功,怎样防止回归。

“系统压力较大,所以加了缓存”不算完整故障。必须能说清压力在哪一层、证据是什么,以及缓存失效时系统如何表现。

学习入口: 系统设计模板


六、业务设计题

Q42:异步安装任务怎样保证时效性和最终完成?

回答检查点:

  • 把成功定义为设备完成下载、校验、安装并回执。
  • 使用状态机记录 REQUESTED → DOWNLOADING → VERIFYING → INSTALLING → INSTALLED/FAILED
  • 回执推进正常路径,超时扫描、有限重试和补偿处理异常路径。
  • 监控端到端耗时、成功率、卡住率、重试率和补偿成功率。

Q43:内容审核 API 有长度和 QPS 限制,怎么处理?

回答检查点:

  • 按结构和语义切分,保留文档 ID、原文 Offset 和必要上下文。
  • 使用令牌桶和有界并发控制 QPS。
  • 429、部分 5xx 和超时按预算退避;参数和鉴权错误直接失败。
  • 聚合片段结果时保留风险等级、位置、置信度和人工复核入口。

Q44:不同厂商数据怎样接入并保证公网安全?

回答检查点:

  • 每个厂商由独立 Adapter 处理认证、分页、重试和字段映射。
  • 业务层只依赖统一 Canonical Schema,并定义主键、枚举、版本和去重规则。
  • 公网接入使用 TLS、签名或 OAuth/mTLS、防重放、最小权限和限流。
  • 记录审计日志并对敏感数据脱敏。

Q45:红包、库存等热点资金操作怎样保证正确?

回答检查点:

  • 金额使用最小货币单位的整数。
  • 在事务或原子脚本中检查幂等、余额、剩余数量并完成扣减。
  • 随机分配算法只决定金额分布,不能代替并发控制。
  • Redis 预扣减方案还需要数据库落账、对账和故障补偿。

学习入口: 系统设计模板


七、算法题

Q46:反转链表怎么做?

作答要点: 用三个指针 prev/cur/next 遍历,每步把 cur.next 指向 prev,再整体后移一格;遍历结束后 prev 就是新的头节点。时间复杂度 O(n),空间复杂度 O(1)。递归写法本质相同,只是用调用栈代替显式指针后移。

现场追问: 只反转链表中间一段怎么做?带环链表怎样先判断再处理?

Q47:两数之和怎么做?

作答要点: 遍历数组时用哈希表记录“数值 → 下标”;对当前数 x,先查表里是否已有 target - x,有则直接返回两个下标,没有再把 x 存入表中。一次遍历、O(n) 时间、O(n) 空间,比双层暴力枚举的 O(n²) 快一个数量级。

现场追问: 如果数组有序,能否把空间复杂度降到 O(1)?如果要求返回所有不重复组合怎么变化?

Q48:无重复字符的最长子串怎么做?

作答要点: 用滑动窗口维护“窗口内无重复字符”的不变量,哈希表记录每个字符最近出现的位置;右指针扫描到重复字符时,左指针跳到 max(left, 上次位置+1),不能直接后退。时间复杂度 O(n)。完整推导、手工运行表和代码见 后端基础:JVM、Spring、网络与并发 · 16.7 用算法训练"状态如何移动"

现场追问: 为什么左指针不能后退?字符串是 Unicode 时怎样处理?

Q49:二叉树层序遍历怎么做?

作答要点: 用队列做 BFS;每轮先记录当前队列长度 size,只弹出这一批节点并收集值,它们的子节点入队留给下一轮处理,从而按层切分。时间复杂度 O(n)。完整代码见 后端基础:JVM、Spring、网络与并发 · 16.7 用算法训练"状态如何移动"

现场追问: 怎样实现之字形层序遍历?最大空间复杂度由什么决定?

Q50:0/1 矩阵最大矩形怎么做?

作答要点: 逐行把每列连续 1 的高度累积成柱状图,再对每一行的高度数组用单调递增栈求最大矩形——弹栈时用栈顶的新栈顶作左边界、当前下标作右边界计算宽度。总复杂度 O(mn)。首尾补高度为 0 的哨兵可以省去边界判断,完整推导见 后端基础:JVM、Spring、网络与并发 · 16.7 用算法训练"状态如何移动"

现场追问: 弹栈时矩形宽度怎样计算?为什么要添加高度为 0 的哨兵?


八、项目与个人经历题

Q51:请介绍你最有代表性的项目

按下面顺序回答:

业务目标和用户
→ 系统规模与关键约束
→ 你负责的具体链路
→ 遇到的一个真实问题
→ 方案选择和代码改动
→ 验证结果
→ 你的职责边界

不要只列技术栈,也不要把团队成果全部说成个人成果。

Q52:你亲自实现过哪些接口?

至少说清:

  • 接口名和调用方向;
  • 请求、响应和主要校验;
  • 数据库表、索引或消息 Topic;
  • 关键函数和修改目录;
  • 错误处理、测试与上线验证。

只回答“我做过用户管理模块”无法证明实际参与。

Q53:描述一次你提交过的代码变更

回答中应出现:

  • 为什么要改;
  • 修改了哪些目录和关键函数;
  • 遇到了什么具体问题;
  • Review 意见是什么;
  • 最终怎样修改和验证。

命令记不全可以解释,代码边界、数据流和修改原因不能始终含糊。

Q54:AI 生成代码比例多少?你怎样做 Code Review?

推荐回答思路:

  • 不把生成比例当成贡献指标;
  • 说明 AI 用在样板代码、测试草稿、检索或重构候选中的哪些环节;
  • 接口契约、权限、并发、迁移和最终合入由谁负责;
  • 给出真实运行过的测试、静态检查和 Review 流程。

Q55:如何回答挫折、学习能力和一年内目标?

使用“问题 → 原因 → 行为改变 → 后续验证”的顺序。少说“抗压、学习快、愿意加班”这类标签,多说一个可以核验的行为变化。


题源说明

本章题目来自用户整理的后端、RAG、Agent 与系统设计面试资料,并结合正文覆盖范围去重、改写。题目保留真实追问方向;回答检查点用于自测,不代表可以替代对正文原理和实际项目证据的学习。