蚂蚁集团后端开发面经合集:网络协议、锁升级与 AI 工程
- 轮次
- 多轮面试合集
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
面经 01 · 后端开发岗(2026-03-28)
- 在浏览器中键入一串网址之后,会发生什么?
- TLS 加密的过程是什么样的?
- CA 证书到底有什么用?
- 为什么 CA 证书能保证公钥传输的安全性?
- 为什么 Synchronized 关键字中要设计锁升级?
- 聊聊锁升级过程?
- 为什么不是所有场景都用重量级锁,而是中间还有一层轻量级锁?
- 为什么偏向锁要升级为轻量级锁?
- 有用过线程池嘛?核心线程数该怎么确定?
- 你觉得在 IO 密集型的操作中,为什么不设置 10 倍的线程数?
- 进程切换需要开销嘛?和线程切换的开销比起来哪个大?
- 为什么 B+ 树的树高能让它的查询效率比红黑树好?
- 为什么要有 Redo Log,而不是直接刷入到磁盘?
- 除了 RAG 还可以用什么方法让模型能接入你自己的文档?
- Langchain4j 在这个过程中具体做了什么工作?
- 了解过 MCP 嘛?
- 了解过 Skill 嘛?
面经 02 · C++ 后端开发(2026-03-19)
- 讲一下你做过的项目,重点说说其中最难的技术点。
- 我看你实习了一年了,怎么没留在上家公司?
- 说一下线程、进程、协程的区别,如果放到你的项目里会怎么选?
面经 03 · Java 后端开发(2026-03-17)
- 离职原因是什么?
- 在你参与的项目中,你觉得有哪些是有挑战性的?
- 针对具体的重构项目,你具体是怎么做的?重构时,业务逻辑和数据库部分是如何处理的?
- 在你的业务场景下,单机和集群的并发(QPS)表现如何?
- 你在平时开发中有接触或使用过 AI 编程吗?
- 在修改现有系统代码时,你一般会如何运用 AI 工具?
- 在使用 AI 时,有遇到过上下文污染或压缩导致的准确性问题吗?
- 从你的使用经验来看,你觉得 Skill 和 Rule 有什么区别?
- 你对幂等性有了解吗?在创单场景下,你们的幂等具体是怎么实现的?幂等 ID 是在什么时间点生成的,由谁负责生成?
- 分布式事务有了解吗?你们系统中 TCC 解决的是哪两个系统之间的问题?
- 如果让你去实现一套幂等机制,你知道该怎么做吗?
- 你们团队内部是如何定义「可用性」这一指标的?
- 如果监控到某个接口的可用性不达标,具体的治理流程是怎样的?
- 用英语口语,用两分钟时间介绍一下你现在的工作内容?
面经 04 · C++ 后端开发(2026-03-01)
- 详细介绍一下你简历中的某个项目,包括技术选型、架构设计、遇到的难点和解决方案。
- 有一个 100GB 的文件,内存只有 4GB,如何找出文件中出现次数最多的前 10 个数字?
- 设计一个近似算法,只读一遍文件就能近似找出出现次数最多的前 10 个数字。什么情况效果好?什么情况效果差?
- 详细说明外部归并排序的原理和实现步骤。
- 手写代码:实现有向无环图(DAG)的深拷贝。
- 介绍一下你做过的最有挑战性的项目,重点说说系统设计和性能优化。
- 如何设计一个分布式限流系统?需要考虑哪些问题?
- 手写代码:实现一个线程安全的单例模式,要求支持延迟初始化和参数传递。
- 如何实现一个高性能的内存池?需要解决哪些问题?
- 设计一个分布式 ID 生成器,需要满足哪些要求?如何实现?
面经 05 · 后端开发(2026-01-20)
- 介绍一个你做过的项目。
- 如何实现一个高效的哈希表,提供哪些接口、哪些成员?
- 哈希表的性能影响因素最大的是什么?10000 个 bucket 和 100 个 bucket 的哈希表性能有什么差距?
- 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(无趋势、索引不友好)。选型取决于是否需要有序、可读与业务语义。