13. 面试真题与自测
十三、面试真题与自测¶
这一章只做两件事:
- 保留接近真实面试现场的问法;
- 告诉你回答时必须覆盖哪些关键点。
原理、代码和完整案例仍放在前面的教学正文中。建议先遮住“回答检查点”,用 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会中断正常执行流程,沿调用栈向上传播,依次执行每一层已注册的defer;recover只有在defer函数内部直接调用才有效,间接调用(比如在被defer调用的函数内部再调用一个函数去recover)无法捕获 panic。recover之后函数会正常返回,不会自动恢复到 panic 发生的位置继续执行;跨 goroutine 的 panic 无法被其他 goroutine 的recover捕获,必须在该 goroutine 自己的defer中处理。
继续追问: 为什么 HTTP 服务的每个请求 goroutine 通常要单独 defer recover?recover 之后怎样保留原始错误堆栈?
三、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 秒、部门服务应该设多少?数据库超时放在哪里?
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:讲一个真实故障应该怎么讲?¶
回答顺序:
- 现象:用户看到了什么,哪些指标异常。
- 复现:在什么条件下稳定出现。
- 证据:日志、Trace、SQL、指标或代码分别证明了什么。
- 根因:具体机制和触发条件。
- 修改:改了哪些文件或配置,为什么有效。
- 验证:怎样证明修复成功,怎样防止回归。
“系统压力较大,所以加了缓存”不算完整故障。必须能说清压力在哪一层、证据是什么,以及缓存失效时系统如何表现。
学习入口: 系统设计模板
六、业务设计题¶
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 与系统设计面试资料,并结合正文覆盖范围去重、改写。题目保留真实追问方向;回答检查点用于自测,不代表可以替代对正文原理和实际项目证据的学习。