面灵AI→

蚂蚁集团后端开发面经合集:网络协议、锁升级与 AI 工程

轮次
多轮面试合集
时间
2026-09
来源
牛客网

《面试题目》

面经 01 · 后端开发岗(2026-03-28)

  1. 在浏览器中键入一串网址之后,会发生什么?
  2. TLS 加密的过程是什么样的?
  3. CA 证书到底有什么用?
  4. 为什么 CA 证书能保证公钥传输的安全性?
  5. 为什么 Synchronized 关键字中要设计锁升级?
  6. 聊聊锁升级过程?
  7. 为什么不是所有场景都用重量级锁,而是中间还有一层轻量级锁?
  8. 为什么偏向锁要升级为轻量级锁?
  9. 有用过线程池嘛?核心线程数该怎么确定?
  10. 你觉得在 IO 密集型的操作中,为什么不设置 10 倍的线程数?
  11. 进程切换需要开销嘛?和线程切换的开销比起来哪个大?
  12. 为什么 B+ 树的树高能让它的查询效率比红黑树好?
  13. 为什么要有 Redo Log,而不是直接刷入到磁盘?
  14. 除了 RAG 还可以用什么方法让模型能接入你自己的文档?
  15. Langchain4j 在这个过程中具体做了什么工作?
  16. 了解过 MCP 嘛?
  17. 了解过 Skill 嘛?

面经 02 · C++ 后端开发(2026-03-19)

  1. 讲一下你做过的项目,重点说说其中最难的技术点。
  2. 我看你实习了一年了,怎么没留在上家公司?
  3. 说一下线程、进程、协程的区别,如果放到你的项目里会怎么选?

面经 03 · Java 后端开发(2026-03-17)

  1. 离职原因是什么?
  2. 在你参与的项目中,你觉得有哪些是有挑战性的?
  3. 针对具体的重构项目,你具体是怎么做的?重构时,业务逻辑和数据库部分是如何处理的?
  4. 在你的业务场景下,单机和集群的并发(QPS)表现如何?
  5. 你在平时开发中有接触或使用过 AI 编程吗?
  6. 在修改现有系统代码时,你一般会如何运用 AI 工具?
  7. 在使用 AI 时,有遇到过上下文污染或压缩导致的准确性问题吗?
  8. 从你的使用经验来看,你觉得 Skill 和 Rule 有什么区别?
  9. 你对幂等性有了解吗?在创单场景下,你们的幂等具体是怎么实现的?幂等 ID 是在什么时间点生成的,由谁负责生成?
  10. 分布式事务有了解吗?你们系统中 TCC 解决的是哪两个系统之间的问题?
  11. 如果让你去实现一套幂等机制,你知道该怎么做吗?
  12. 你们团队内部是如何定义「可用性」这一指标的?
  13. 如果监控到某个接口的可用性不达标,具体的治理流程是怎样的?
  14. 用英语口语,用两分钟时间介绍一下你现在的工作内容?

面经 04 · C++ 后端开发(2026-03-01)

  1. 详细介绍一下你简历中的某个项目,包括技术选型、架构设计、遇到的难点和解决方案。
  2. 有一个 100GB 的文件,内存只有 4GB,如何找出文件中出现次数最多的前 10 个数字?
  3. 设计一个近似算法,只读一遍文件就能近似找出出现次数最多的前 10 个数字。什么情况效果好?什么情况效果差?
  4. 详细说明外部归并排序的原理和实现步骤。
  5. 手写代码:实现有向无环图(DAG)的深拷贝。
  6. 介绍一下你做过的最有挑战性的项目,重点说说系统设计和性能优化。
  7. 如何设计一个分布式限流系统?需要考虑哪些问题?
  8. 手写代码:实现一个线程安全的单例模式,要求支持延迟初始化和参数传递。
  9. 如何实现一个高性能的内存池?需要解决哪些问题?
  10. 设计一个分布式 ID 生成器,需要满足哪些要求?如何实现?

面经 05 · 后端开发(2026-01-20)

  1. 介绍一个你做过的项目。
  2. 如何实现一个高效的哈希表,提供哪些接口、哪些成员?
  3. 哈希表的性能影响因素最大的是什么?10000 个 bucket 和 100 个 bucket 的哈希表性能有什么差距?
  4. spinlock 和 mutex 的底层原理是什么?

《参考解析》

面经 01 · 网络与并发基础

1. 从输入 URL 到页面渲染的完整链路:URL 解析 → 逐级查缓存(浏览器缓存、DNS 缓存)→ DNS 递归解析拿到 IP → 三次握手建立 TCP → TLS 握手(HTTPS)→ 发送 HTTP 请求 → 服务端处理并回响应 → 浏览器解析 HTML 构建 DOM、CSSOM,合成渲染树,布局、绘制、栅格化上屏。这条链路的回答价值在于能顺手指出优化点:DNS 预解析与连接复用(Keep-Alive、HTTP/2 多路复用)、CDN 就近接入、静态资源预加载、关键渲染路径优化(减少阻塞渲染的 CSS/JS)。面试里最好按「解析、连接、请求、渲染」四段讲,每段点一个可观测指标,而不是背八股。

2. TLS 握手与 CA 证书的信任链:TLS 1.2 完整握手是 ClientHello(随机数 + 密码套件)→ ServerHello + 证书 + 密钥交换参数 → 客户端校验证书、生成 pre-master secret 并用服务端公钥加密回传 → 双方由三个随机数导出会话密钥 → ChangeCipherSpec 与 Finished 验证握手完整性;TLS 1.3 压缩到 1-RTT(会话复用可 0-RTT),并把密钥交换统一到 ECDHE 以获得前向安全。CA 的作用是给「这个公钥属于这个域名」背书:服务端证书由 CA 私钥签名,客户端用内置根证书逐级验签、校验证书链、域名、有效期与吊销状态(CRL/OCSP)。所以安全性并不来自公钥加密本身,而来自信任链——中间人拿不到 CA 私钥就签不出能通过校验的证书;又因为用 ECDHE,服务端长期私钥即使泄露也解不开历史流量。

3. Synchronized 锁升级的设计动机与升级过程:早期 Synchronized 直接用操作系统互斥量实现,加锁要陷入内核、竞争失败要挂起线程再唤醒,代价高。但绝大多数加锁场景根本没竞争,甚至永远只有一个线程进入。于是 HotSpot 设计了锁升级:无锁 → 偏向锁(Mark Word 里记线程 ID,同一线程再次进入几乎零成本)→ 轻量级锁(CAS 把 Mark Word 替换为指向栈上 Lock Record 的指针,有竞争先自旋)→ 重量级锁(自旋失败后向操作系统申请 monitor,没抢到的线程阻塞排队)。本质是把「无竞争」和「短暂竞争」这两种高频情况用纯用户态手段解决,只有真正激烈竞争才付内核态代价。注意偏向锁撤销要等到安全点,批量撤销在竞争激烈或大量对象创建的场景下反而拖累性能,JDK 15 之后已默认关闭。

4. 线程池核心线程数的确定与 IO 密集型的误区:CPU 密集型任务线程数取 CPU 核数或核数 +1,再多只是增加上下文切换;IO 密集型可以按「核数 ×(1 + 等待时间 / 计算时间)」估算,等待占比越高可开的线程越多。但公式只是起点:线程数上限还受下游连接池与数据库最大连接数、每线程约 1MB 栈内存、GC 压力和切换成本约束。IO 密集型不无脑开 10 倍线程的原因是,10 倍是经验值不是定律——线程数超过下游承载能力后,瓶颈从 CPU 转移到连接池排队,请求只是换个地方等,还多付了调度与栈内存开销。正确做法是先压测找到真正的瓶颈资源(CPU、磁盘、DB 连接数、下游限流),按瓶颈定线程数,并配有界队列、拒绝策略与线程池隔离,避免一个慢接口拖垮全部。

5. B+ 树高度低为什么查询更快:红黑树是二叉结构,同样数据量高度约为 log2(N),千万级数据有 20 多层;B+ 树每个节点是一个磁盘页(InnoDB 默认 16KB),非叶子节点只存键和指针,一页能放几百上千个键,扇出可达数百,所以 34 层就能覆盖千万级数据。差别不只是比较次数,而是磁盘 IO 次数:B+ 树查一条记录通常 34 次页读取,且非叶子节点常驻缓冲池后真正落盘的往往只有最后一两次,红黑树则每层都可能是一次随机 IO。再加上叶子节点用双向链表串联,范围查询与排序可以顺序扫描,这也让它在 OLTP 场景优于 B 树与哈希索引。空间局部性是同一条逻辑:一次页读取能覆盖大量相邻键,缓存命中率远高于指针跳转。

6. Redo Log 为什么必须存在:InnoDB 以页为单位修改数据,随机写一页 16KB 很慢,如果事务提交时必须把脏页刷盘,随机写放大严重。Redo Log 走 WAL 思路:先把「对某个页做了什么修改」顺序追加进日志(顺序写、单条记录小),事务提交只需保证日志落盘(innodb_flush_log_at_trx_commit=1),脏页交给后台线程批量刷。收益有四点:顺序写替代随机写;崩溃后能重放已提交事务,保证持久性;把一次事务的多次随机写合并成批量刷盘;配合 undo log 与 binlog 的两阶段提交保证主从一致与回滚能力。代价是要理解 LSN 与 checkpoint 机制——checkpoint 推进太慢会导致崩溃恢复时间变长,这才是 Redo Log 里真正需要监控的部分。

7. 大模型接入自有文档:RAG 之外的路:RAG 只是最主流的一条路。可选方案还有:① 微调(SFT/LoRA)把领域表达与输出格式内化,适合风格与固定任务,不适合频繁变化的事实;② 长上下文直接灌入,适合文档集小、临时性问答,成本随长度线性上涨且有中间信息遗忘;③ 先把文档做结构化抽取入库,再用 Text2SQL 或工具调用查询,适合需要精确筛选与计算的场景;④ 图谱增强(GraphRAG)显式建实体关系,适合多跳推理与全局摘要;⑤ Agentic RAG,让模型自己决定检索时机与查询改写,多轮补齐证据。工程上通常是组合:结构化数据走 SQL/API,非结构化走向量检索,上面再叠重排与引用校验。最终用带标注的评测集决定取舍,而不是凭感觉选型。

8. LangChain4j 在 RAG 链路上承担的工作:它是 Java 生态的 LLM 应用框架,把 RAG 拆成可替换组件:Document Loader 读 PDF/Word/网页并转成带 metadata 的 Document;Document Splitter 做分块(按字符、递归、段落,配 overlap);EmbeddingModel 负责向量化;EmbeddingStore 对接 Redis、PGVector、Milvus 等向量库;ContentRetriever 负责召回;AiServices 把「检索 → 拼 prompt → 调模型 → 返回」编排成声明式接口。此外还提供 ChatMemory 管理多轮上下文、工具注册与 Function Calling、结构化输出解析和流式响应。它的真正价值是标准化这些环节,让换模型、换向量库、加 rerank 或查询改写不必把胶水代码写死在业务里;但分块策略、检索参数与评测仍要自己设计,框架不会替你保证效果。

9. MCP 与 Skill 的边界:MCP(Model Context Protocol)是模型与外部能力之间的协议层,用标准 server 暴露 tools、resources、prompts,客户端按协议发现并调用,解决的是「能力怎么接进来」的互操作问题——一次实现,多个宿主可用。Skill 更偏「知识与流程的封装」,把某类任务的做法、脚本、模板和约束打包成按需加载的资产,解决的是「这类活该怎么干」。两者可以叠加:Skill 描述流程并在需要时调用 MCP 提供的工具。选型上,需要跨工具复用、要访问外部有状态系统时用 MCP;需要沉淀团队规范、可复用的操作步骤时用 Skill。实践中最容易踩的坑是工具描述写得太糙导致模型选错工具,以及不设权限边界就让模型随意调用写操作。

面经 03 · 幂等、分布式事务与可用性

10. 创单场景的幂等实现与幂等 ID 的生成时机:核心目标是「同一次业务请求只产生一个业务结果」。常见做法:客户端进入下单页或点击提交时先申请全局唯一的幂等 ID(UUID 或雪花 ID),由服务端或网关生成并下发;请求携带该 ID 打到创单接口,服务端以「幂等 ID + 业务类型」建唯一索引,插入成功才继续后续业务,重复请求命中唯一键就直接返回首次结果。生成时机越靠前越好:放在点击提交时或更早,才能覆盖重复点击、网络重试、超时重发三种情况;如果等到服务端落库时才生成,重试请求会拿到新 ID,幂等直接失效。工程细节:幂等记录要有过期清理策略;处理中与成功要区分开,用状态机而不是「已成功」一刀切;跨库跨服务时把幂等表与业务表放同一本地事务,或用本地消息表 + 重试保证最终一致。

11. TCC 解决的是哪两个系统之间的问题:TCC 是补偿型分布式事务,把一次分布式操作拆成两阶段:Try 阶段各参与方做资源预留(冻结库存、冻结额度、预创建订单),Confirm 阶段确认扣减,任一 Try 失败则 Cancel 释放预留。它解决的是跨服务的业务数据一致性,典型场景是交易与库存、交易与账务之间的扣减与入账——这些系统各有独立数据库,本地事务包不住,而 XA/2PC 在高并发下锁持有时间太长、可用性差。TCC 的难点在于:Try 必须预留而不是直接扣减,否则 Cancel 无法回滚;Confirm 与 Cancel 都要幂等;空回滚与悬挂要靠事务控制表加防悬挂校验处理;业务上要接受最终一致而不是强一致。因此它适合资金、库存这类必须精确对账的核心链路,不适合无脑铺开。

12. 可用性指标的定义与不达标治理:可用性通常按「成功请求数 / 总请求数」计算,难点是先把「成功」定义清楚:只看 HTTP 状态码会漏掉业务失败(返回 200 但业务码非成功、超时、返回空数据),要结合接口契约与核心链路定义黄金指标——QPS、错误率、P99 延迟、饱和度——并按接口、调用方、机房拆分。还要区分「真失败」与「可容忍失败」:降级返回兜底值算成功还是失败,必须在 SLO 里写清楚,否则数据没法用于决策。治理流程一般是:告警触发 → 看板定位范围(单接口、整体、依赖故障还是流量突增)→ 止血优先(限流、降级、切流、回滚)→ 恢复后定位根因(变更、依赖、容量、脏数据)→ 补监控与预案并做回归。原则是先恢复再定位,每次故障都要沉淀可执行的预案,而不是只交一份复盘文档。

面经 04 · 设计题与手撕

13. 100GB 文件求 Top10:分治、近似算法与外部排序:标准解法是哈希分桶加分治:第一遍按 hash(数字) % N 把数据散到 N 个小文件,保证相同数字必然落在同一文件、单文件可读进内存;第二遍逐文件统计频率,每个文件用大小为 10 的最小堆维护 Top10,最后把所有文件的 Top10 归并。整体 O(n) 时间,内存取决于桶数与单桶去重后的键数。若要求只扫一遍且内存固定,就用近似算法:Space-Saving 维护 k 个计数器,遇到表中没有的元素时替换当前计数最小的那个并记录误差,候选结果天然偏向高频项;Count-Min Sketch 用多组哈希的二维计数器做频率估计,只会高估不会低估。近似算法在热点明显、第 10 名与后续频率差距大时效果好;数据分布均匀或第 10 名与第 11 名频率接近时误差很大。如果要求输出全量有序结果,就用外部归并排序:分块内存排序写回临时文件,再用最小堆做多路归并,优化点全在减少磁盘 IO——加大读写缓冲、顺序读写、控制归并路数与轮次。

14. 分布式限流系统的设计要点:算法层:固定窗口实现简单但有临界突刺(窗口边界可能出现双倍流量),滑动窗口用 Redis ZSet 或分桶计数更平滑但内存与复杂度更高,令牌桶允许突发、漏桶强制匀速,按「保护下游」还是「削峰」来选。分布式一致性靠 Redis + Lua 把「取令牌、计数、写回」做成原子操作,或用集群分片避免单点;同时保留本地限流做第一道防线,既挡单机热点也降低 Redis 压力,两层阈值叠加时要留余量。规则通过配置中心下发,支持按接口、用户、租户、来源 IP 多维度,限流键的设计直接决定粒度是否合理。最容易被忽略的是降级:Redis 故障不能把业务一起拖死,要能切本地限流或直接放行并告警;返回给调用方的应是明确的限流错误码与 Retry-After,而不是让它等到超时。

15. 分布式 ID 生成器的要求与实现:基本要求是全局唯一、趋势递增(有利于 B+ 树索引与范围查询)、高性能低延迟、高可用、无敏感信息且长度可控。主流方案是雪花算法:1 位符号 + 41 位毫秒时间戳 + 10 位机器 ID + 12 位序列号,单机每毫秒 4096 个,本地生成无网络开销且趋势递增。工程上必须处理:时钟回拨(小幅回拨等待或改用备用位,大幅回拨直接拒绝并告警)、机器 ID 分配(ZK/etcd/配置中心或 StatefulSet 序号,重复会直接产出重复 ID)、序列号耗尽时自旋到下一毫秒、NTP 校时抖动。备选方案有号段模式(数据库批量取号,递增但依赖 DB)、Redis INCR(简单但依赖网络与持久化)、UUID(无趋势、索引不友好)。选型取决于是否需要有序、可读与业务语义。