AI Agent 算法一面面经
完整信息
本篇目录
写在前面
【WeThinkIn出品】栏目专注于分享Rocky的认知思考与经验感悟,范围涵盖但不限于AI行业。
干货AIGC、AI Agent、LLM算法工程师/开发工程师面试面经秘籍分享(持续更新):WeThinkIn/AIGC-Interview-Book欢迎大家Star~
大家好,我是Rocky Ding。
一面
1. KV Cache是什么?为什么能加速大模型推理?
面试官: 有了解过KV Cache吗?它是什么作用,为什么能加速大模型推理?
面试官追问: 那KV Cache的代价是什么?长序列推理时显存怎么估算?
回答:
KV Cache 的本质,是用显存换取自回归解码阶段的重复计算。在 Transformer 的每一层注意力中,历史 Token 对应的 Key 和 Value 一旦计算完成,在后续生成步骤中通常不会改变,因此可以缓存下来。生成第 $t$ 个 Token 时,只需要计算当前 Token 的 Query、Key、Value,再让当前 Query 与前 $t$ 个位置的 Key、Value 做注意力,而不必重新计算全部历史 Token 的 Key、Value 投影。
需要精确区分两个阶段:
- Prefill 阶段。 一次输入整段 Prompt,普通全注意力的计算量仍近似为 $O(T^2)$。KV Cache 不会消除这部分完整上下文注意力计算。
- Decode 阶段。 每生成一个新 Token,只新增一份 K、V,并读取历史缓存。单步注意力计算随当前上下文长度近似为 $O(T)$;如果完全不缓存,还要反复重算历史 Token 的 K、V 和中间表示,代价会显著更高。
因此,不能简单说“整个生成过程从 $O(T^2)$ 变成 $O(T)$”。更严谨的说法是:KV Cache 避免了逐步解码中对历史 K、V 的重复投影与重复前向计算,使增量解码成为可能,但每个新 Query 仍需要与历史 Key 做注意力。
标准 Decoder-only 模型的 KV Cache 理论容量可近似写为:
<div>$M_{\mathrm{KV}} = 2LBTH_{\mathrm{KV}}D_hS,$
</div>其中:
- $2$ 表示 Key 和 Value 两份缓存;
- $L$ 是 Transformer 层数;
- $B$ 是实际并发序列数;
- $T$ 是每条序列当前缓存的 Token 数;
- $H_{\mathrm{KV}}$ 是 KV Head 数;
- $D_h$ 是每个 Head 的维度;
- $S$ 是每个元素的字节数,例如 FP16/BF16 通常为 2 字节。
例如一个 32 层、32 个 KV Head、Head Dimension 为 128 的模型,在 Batch Size 为 1、上下文为 8192、FP16 存储时,理论 KV Cache 约为:
<div>$2\times32\times1\times8192\times32\times128\times2 \approx 4\ \mathrm{GiB}.$
</div>但这不是所有 7B 模型的固定答案。采用 GQA 或 MQA 后,$H_{\mathrm{KV}}$ 会远小于 Query Head 数,容量可明显下降;PagedAttention、块分配、前缀复用、量化 KV Cache、滑动窗口注意力和 Tensor Parallel 也会改变物理占用。生产估算还要加入 Block 内部碎片、运行时元数据、Beam Search 复制、并发请求峰值及安全余量。
KV Cache 的主要代价包括显存线性增长、内存带宽压力、长上下文下的碎片与调度复杂度,以及多并发场景中更低的可容纳 Batch。面试时可以用一句话收束:KV Cache 省掉的是历史状态的重复计算,但没有让历史上下文本身消失,长上下文推理最终会从算力瓶颈逐步转向显存容量和带宽瓶颈。
2. Agent上下文超限了怎么处理?
面试官: Agent执行多步任务时,对话历史加工具返回结果很容易超限,你有什么处理方案?
面试官追问: 三种策略分别在什么场景下用?
回答:
Agent 上下文治理的核心不是“把旧消息删掉”,而是在有限 Token 预算内,持续保留完成当前任务所必需的状态、证据和约束。一个成熟方案通常分为五层。
第一层是预算与优先级管理。先为 System Instruction、工具 Schema、当前用户请求、近期对话、任务状态、检索证据和输出预留独立预算。System 约束、权限边界和当前目标不能被普通历史消息挤出上下文。
第二层是近期窗口。保留最近若干轮原始消息,适合强依赖局部指代、短期状态快速变化的实时对话。但窗口不应只按“轮数”截断,而应按 Token、消息类型和任务依赖截断。机械丢弃最早消息可能删掉长期有效的用户约束。
第三层是结构化状态与摘要。把历史对话压缩成任务状态,例如目标、已确认事实、未完成子任务、约束、决策及证据引用。摘要应支持版本号、更新时间和可追溯来源;发生冲突时,新事实不能静默覆盖旧事实,而应记录“旧值、当前值、变更依据”。
第四层是外部记忆检索。把长期历史、文档和经验存入可检索存储,按语义、关键词、时间、实体、会话和权限联合召回。向量相似度只表示“像”,不表示“当前有效”,因此还要考虑新鲜度、来源可信度、用户归属和业务权限。
第五层是工具结果压缩。Agent 最容易被日志、网页全文、数据库大结果集和代码构建输出撑爆。应在工具层先做分页、字段投影、去重、统计聚合、错误摘要和 Artifact 引用,只把决策所需片段送回模型,而不是把原始输出全部塞回上下文。
场景选择可以这样回答:
- 短对话和低风险客服:近期窗口加少量结构化槽位即可。
- 长周期任务:近期窗口、任务状态、阶段摘要和 Artifact 引用组合使用。
- 跨会话个性化:使用权限隔离的长期记忆检索,并设置过期、确认和删除机制。
- 知识问答:重点是检索外部证据,而不是把所有历史对话永久保留。
- 高风险操作:关键参数、用户确认、审批结果和工具回执必须保留为不可丢失的结构化状态。
摘要模型不一定越小越好。小模型成本低,但数字、否定关系、权限条件和未完成事项一旦压错,后续所有步骤都会在错误状态上继续。应使用专门评测集比较事实保真率、未完成任务召回率、压缩率和下游成功率,再决定模型与策略。上下文压缩的目标不是最短,而是让 Agent 在更少 Token 中仍然保持任务连续性、事实一致性和可恢复性。
3. Agent取消机制怎么做?
面试官: 一个Agent任务耗时很长,用户等不及了,怎样停止这个任务?这个机制你实际尝试过吗?
回答:
Agent 取消机制的本质是可传播的协作式取消 + 资源级强制终止 + 状态一致性恢复。只在主循环里放一个布尔标记,只能停止下一步调度,不能保证当前网络请求、子进程、数据库事务或外部工具真正停下来。
完整设计可以分为六层:
- 任务状态机。 每个任务具有
task_id、RUNNING/CANCELLING/CANCELLED/SUCCEEDED/FAILED状态、截止时间和父子任务关系。取消请求必须幂等,多次取消返回同一个最终状态。 - 取消令牌传播。 从 HTTP 请求或交互层生成 Cancellation Token,向 Planner、Agent Loop、模型流式请求、工具调用和子任务逐层传播。每个耗时阶段都在安全点检查,而不是只在循环开头检查。
- Deadline 与超时。 将剩余时间预算传给下游,区分连接超时、首 Token 超时、空闲读取超时和总耗时。超时本质上是由系统发起的取消,也必须走同一条清理链路。
- 底层中止。 对支持取消的 HTTP 客户端主动关闭请求或流;对子进程先发送优雅终止信号,超时后再强制终止进程组;对容器或沙箱可以直接回收执行单元。
Future.cancel()或Thread.interrupt()只是发出信号,目标代码不响应时并不等于任务已经停止。 - 副作用治理。 已执行的发邮件、扣款、写文件等动作不能靠取消“回滚”。工具应支持幂等键、事务、预提交、补偿动作和操作日志。取消时停止未来动作,并对已提交副作用执行明确的补偿或人工确认。
- 资源清理与观测。 在
finally、whenComplete或上下文管理器中释放连接、文件句柄、锁、临时目录和 GPU Session;记录取消来源、传播延迟、未能中止的子任务和最终一致性结果。
Java 中 CompletableFuture.orTimeout() 只会让 Future 以超时异常完成,通常不会自动停止已经运行的底层任务;丢弃 Future 更不会释放连接。正确做法是让执行函数显式接收取消令牌或 Deadline,并使用能够中止请求的客户端 API。对于不可协作的第三方 SDK,应把调用隔离到独立进程、容器或受控 Worker,通过终止执行单元形成最后一道边界。
实际项目回答应给出可验证证据,例如“取消请求 P95 多久生效”“正在进行的工具是否真正停止”“已产生副作用如何恢复”,而不是只说自己使用了某个并发类。面试式结论是:取消不是线程 API 问题,而是从用户请求一直传播到资源和副作用的一条系统契约。
4. MCP是什么?Agent通过MCP调用工具的流程是什么?
面试官: MCP是什么?Agent通过MCP调用工具的流程是什么?
面试官追问: 你觉得MCP能成为标准吗?
回答:
MCP,即 Model Context Protocol,是一种面向 AI 应用与外部能力之间连接的开放协议。它解决的不是模型“会不会决定调用工具”,而是 Host、Client 与 Server 之间如何发现能力、协商版本、传输请求、返回内容并管理连接生命周期。Tools、Resources、Prompts 是常见的 Server 能力;较新的协议还在能力协商下支持日志、Sampling、Elicitation、Tasks 等扩展。
更准确的角色关系是:
- Host 是承载 Agent 的应用,例如 IDE、桌面客户端或业务服务,负责权限、上下文、模型调用和用户交互。
- MCP Client 运行在 Host 内部,通常与一个 MCP Server 建立一个逻辑连接。
- MCP Server 暴露工具、资源或提示模板,并连接本地文件、数据库、SaaS 或企业系统。
典型调用流程如下:
- Client 通过
initialize与 Server 协议握手,协商协议版本和双方能力,然后发送 initialized 通知。 - Host 根据需要通过
tools/list等方法发现能力,将经过权限过滤的工具 Schema 提供给模型。 - 模型基于用户请求生成结构化工具调用意图。注意,这一步通常属于模型 API 或 Agent Runtime,而不是 MCP 协议替模型做决策。
- Host 校验工具名、参数 Schema、用户权限、风险级别和是否需要人工批准。
- Client 通过 JSON-RPC 2.0 向 Server 发起
tools/call。本地常见传输是 stdio,远程常见传输是 Streamable HTTP。 - Server 执行能力并返回结构化内容、错误状态或资源引用。Host 对结果做大小限制、脱敏、可信度标记和 Prompt Injection 防护。
- Host 将工具结果关联到原始调用,再交回模型继续推理,直到产生最终回答或触发下一次工具调用。
关于“能否成为标准”,到当前阶段已经不能再回答“OpenAI 和 Google 是否跟进还不确定”。OpenAI Agents SDK 与 Google ADK 均已提供 MCP 集成,主流开发工具和 Agent 生态也在广泛接入。更合理的判断是:MCP 已经成为 Agent 工具与上下文连接层最有影响力的开放协议之一,但“广泛采用”不等于所有系统都必须统一成一个运行时。
它仍有三类边界:第一,协议统一接口,不统一工具语义、数据质量和业务事务;第二,远程 Server 带来授权、Token 转发、供应链和 Prompt Injection 风险;第三,低延迟、强事务、超高吞吐的内部调用仍可能直接使用 gRPC、HTTP 或消息系统。MCP 的长期价值更像 Agent 世界的连接协议,而不是替代所有服务协议。
5. RAG中的父子索引是什么?解决了什么问题?
面试官: RAG为什么需要设计父子索引?检索小文本块、返回大父文档能解决什么问题?
面试官追问: 这样设计有什么代价?
回答:
父子索引解决的是 RAG 中一个非常核心的矛盾:用于匹配的文本块希望足够小,用于回答的上下文又希望足够完整。
如果 Chunk 太大,一个向量同时混入多个主题,查询信号容易被稀释,精确术语也可能匹配不出来;如果 Chunk 太小,虽然局部语义更纯,但标题、定义、前提、表格上下文和跨段逻辑会被切断。父子索引把这两个粒度拆开:
- 以较小的子块建立向量或关键词索引,每个子块记录
parent_id、位置、版本和权限元数据。 - 查询时检索子块,可先混合 BM25 与向量召回,再做 Rerank。
- 将命中的子块映射回父块或相邻窗口,按父文档去重、合并和控制总 Token。
- 把更完整的父级上下文交给生成模型,并保留子块命中位置用于引用和可解释性。
这里的“父”不一定是整篇文档,也可以是一个章节、一个表格加说明、一个函数或一个带标题的语义段落。父块过大仍会引入噪声,因此父子尺寸不能只凭经验固定为 256/1024 Token,应根据文档结构、查询类型和下游评测调优。
它的代价主要包括:
- 索引与文档存储更复杂,需要维护子块到父块的映射、版本和权限一致性。
- 多个命中子块可能指向同一个父块,若不去重会重复占用上下文。
- 返回父块会放大 Token 和延迟,甚至把未授权的兄弟内容一起带出,因此权限应至少落实到可返回的父级粒度。
- 子块的 Rerank 分数不能直接等同于父块分数,需要设计 Max、Top-N 聚合或覆盖度评分。
- 文档更新后,父块与子索引必须原子换版,否则会出现旧索引指向新正文或反过来的不一致。
验证时应比较 Recall@K、MRR/nDCG、答案正确率、引用命中率、上下文利用率、Token 成本和延迟,并对普通小块、滑动窗口、父子索引和层级检索做消融。父子索引不是简单多存一份文本,而是把“在哪里命中”和“带什么上下文回答”拆成两个可分别优化的阶段。
6. ReAct模式和Plan模式有什么区别?
面试官: ReAct模式和Plan模式有什么区别?分别适合什么场景?
回答:
ReAct 与 Plan 模式的本质区别,是“决策与执行交错进行”还是“先形成显式计划,再由执行器推进”。二者不是互斥框架,也不能简单等同于“ReAct 灵活但慢、Plan 一定高效”。
ReAct 通常按 Observation -> Reason/Decision -> Action -> New Observation 循环。每一步都根据最新环境反馈决定下一步,适合信息不完整、需要搜索、工具结果不确定或错误恢复频繁的任务。优势是反馈闭环短;缺点是模型可能局部贪心、重复调用、路径漂移,而且每一步都要重新推理,成本和延迟较高。
Plan-and-Execute 先把目标拆成子任务、依赖和验收条件,再由执行器逐步完成。计划可以是线性列表,也可以是 DAG,但“Plan 模式必然一次性生成完整 DAG”并不准确。优势是全局目标更清晰、任务可并行、便于预算和审计;缺点是环境变化后原计划会过时,错误计划如果不允许重规划,执行越认真可能偏得越远。
场景上可以这样选择:
- 开放式检索、故障排查、网页操作、代码探索:更适合 ReAct,因为下一步依赖刚获得的观察。
- 固定审批流、批处理、可预测的数据流水线:更适合显式 Plan 或 Workflow。
- 长周期复杂任务:通常采用分层混合模式,Planner 生成里程碑和依赖,每个子任务内部使用 ReAct,遇到关键假设失效时触发 Replan。
工程实现中,计划不应只是一段自然语言。至少要有 step_id、依赖、输入、工具权限、预算、完成条件和状态;执行器则要维护最大步数、重试策略、幂等键、Checkpoint 和取消信号。每一步完成后由确定性 Verifier 或环境状态判断是否达标,不能只让同一个模型口头宣布成功。
一个成熟系统还要支持“不要用 Agent”的判断。如果流程稳定、风险高且规则明确,状态机或工作流引擎通常比自主 ReAct 更可靠。ReAct 解决动态探索,Plan 解决全局分解,生产系统真正重要的是反馈、验证和重规划,而不是给循环贴哪一个模式标签。
7. 工具返回结果的消息结构怎么设计?为什么需要单独的Tool Message?
面试官: Agent调用工具后,工具返回结果的消息结构怎么设计?为什么需要单独的Tool Message?
回答:
Tool Message 的核心作用,是把“模型提出的工具调用”和“外部系统返回的观察结果”建立明确、可机器验证的关联。它不是普通用户消息,也不是系统指令。如果把工具输出拼成用户文本,模型、编排器和审计系统都很难判断这段内容来自谁、对应哪次调用、是否执行成功,以及能否被信任。
一个通用的工具结果结构可以包含:
{
"role": "tool",
"tool_call_id": "call_123",
"name": "query_order",
"status": "success",
"content": [{"type": "text", "text": "..."}],
"error": null,
"metadata": {
"latency_ms": 83,
"attempt": 1,
"truncated": false,
"artifact_id": null
}
}
具体字段要服从所使用的模型 API 或协议,不能假设所有厂商都接受自定义 status 和 metadata。通常必须保证:tool_call_id 与模型发出的调用一一对应;结果内容类型明确;业务失败与协议失败可区分;大结果通过 Artifact、分页或引用返回;敏感字段在进入模型前完成脱敏。
单独设计 Tool Message 有五个价值:
- 关联性。 并行调用多个工具时,可把每个结果精确绑定到原始调用。
- 角色边界。 模型知道这是一条外部观察,而不是用户的新指令。
- 可恢复性。 编排器可以根据超时、可重试错误、参数错误或权限错误采用不同策略。
- 可观测性。 调用链能够记录工具、参数摘要、延迟、重试和结果大小。
- 安全性。 工具内容可被标记为不可信数据,避免网页或文档中的恶意文本越权成为高优先级指令。
需要特别强调:Tool Message 虽然角色独立,但并不会自动消除 Prompt Injection。网页、邮件和数据库文本仍可能包含“忽略之前指令”之类内容。Host 必须限制工具权限、隔离数据与指令、对高风险动作二次验证,并避免把原始 HTML、系统密钥或超长日志直接交给模型。
Tool Message 的本质不是多一个 role 字段,而是把工具调用从自然语言聊天提升为可关联、可审计、可恢复的执行协议。
8. 多个Agent同时修改同一个文件,如何避免冲突?
面试官: 多个Agent同时修改同一个文件,如何避免冲突?
回答:
最优先的原则不是“给同一个文件加一把更大的锁”,而是尽量减少共享可变状态。可以先按模块、目录、函数或任务所有权拆分,让多个 Agent 在独立 Workspace、分支或 Git Worktree 中工作,最后通过 Patch 合并。只有确实需要修改同一逻辑区域时,才进入冲突控制。
一种可靠的工程链路是:
- 每个 Agent 从同一个基线提交或文件哈希开始,在隔离工作区产生结构化 Patch。
- 提交前执行 Compare-And-Swap:确认目标文件当前哈希仍等于读取时的基线哈希。
- 若基线未变化,原子应用 Patch;若已变化,使用三方合并,即 Base、Current、Agent Result,而不是覆盖写入。
- 只在“检查版本 + 应用 Patch + 更新版本”的短临界区加锁,避免把模型思考和工具执行时间放进锁内。
- 合并后运行解析器、格式化、类型检查、测试和领域不变量校验。文本无冲突不代表语义无冲突。
乐观并发控制适合冲突概率低、Agent 任务较长的场景;悲观锁适合短事务和高冲突热点,但必须设置租约、持有者 Token、超时和 Fencing Token,避免锁过期后旧持有者继续写入。仅使用 Redis SET NX PX 而没有 Fencing,无法完全防止暂停进程恢复后的陈旧写。
对于代码仓库,Git 的 Patch 和三方合并通常优于直接共享文件锁;对于结构化文档,可以按 AST、JSON Patch 或数据库行级版本合并。所谓“让 LLM 仲裁冲突”只能作为生成候选合并结果,最终仍要由确定性校验和测试验证。
还要处理原子落盘:先写临时文件、fsync,再原子重命名,避免进程崩溃留下半个文件。写操作应带 operation_id,重试时保持幂等。
多 Agent 协同的关键是隔离、版本、Patch、短临界区和验证闭环;锁只是其中一个工具,不能替代语义合并。
9. Agent陷入死循环或重复调用,怎么兜底?
面试官: Agent陷入死循环或重复调用同一个工具,你们的系统有什么兜底机制?
回答:
死循环治理要同时限制资源消耗和识别“没有进展”。固定最大 15 步、同一工具每分钟 5 次可以作为某个业务的配置,但不能当成普适答案。搜索任务、代码任务和高风险交易的合理预算完全不同。
可以设计六层保护:
- 硬预算。 限制总步骤、模型 Token、工具次数、总耗时、金额、外部请求数和并行度。任何一个预算耗尽都进入受控终止,而不是继续请求模型“再试一次”。
- 调用指纹。 对
tool_name + 规范化参数 + 关键环境版本做哈希。连续重复相同调用且环境未变化时,不再次执行副作用工具,而是返回缓存结果或明确告知重复。 - 进展不变量。 观察已解决子目标数、错误类型、文件哈希、数据库状态、测试通过数或检索到的新证据。若若干步后状态没有增量,即使工具参数略有变化,也可能是循环。
- 错误分类与重试策略。 仅对瞬时错误重试,并采用指数退避和随机抖动;参数错误、权限错误和确定性业务拒绝不应盲目重试。
- 熔断与降级。 某工具持续失败时打开 Circuit Breaker,切换备用工具、回退到只读模式、请求用户补充信息或转人工。
- 可恢复终止。 退出前保存 Checkpoint、已完成事项、失败原因和下一步建议,使用户或人工能够从断点继续。
不建议依赖“最近几轮 Thought 语义相似度”作为主要检测手段。一方面模型的隐式推理未必可见或适合持久化,另一方面语言表述可以变化但行为仍然重复。应优先基于可观察的 Action、参数、环境状态和任务进度检测。
副作用工具还必须使用幂等键和执行前置条件。即使 Agent 重复调用,支付、发信、发布和删除操作也不能重复生效。高风险动作应采用准备、确认、提交的两阶段交互,并在提交前再次校验用户授权和当前状态。
真正的兜底不是“发现循环后报错”,而是让系统在任何重复、超时和失败路径下都能控制成本、阻止重复副作用,并留下可恢复状态。
10. 多Agent之间通信方式是什么?如何做任务分发和结果合并?
面试官: 多Agent之间怎么通信?如何做任务分发和结果合并?
回答:
多 Agent 通信首先要选择协作拓扑,而不是先选择 Redis 还是 gRPC。常见结构包括 Supervisor-Worker、Planner-Executor、黑板系统、事件驱动协作和点对点会话。生产中通常优先采用中心编排或共享任务状态,因为它更容易控制权限、预算、重试和审计。
通信方式可按可靠性需求选择:
- 同进程低延迟协作:内存队列、Channel、
asyncio.Queue,但进程退出后状态会丢失。 - 同集群同步调用:gRPC/HTTP,适合短请求和明确响应,不适合把长任务可靠性全部压在连接上。
- 异步可靠任务:Kafka、RabbitMQ、Redis Streams 或云消息队列,支持确认、重试、消费组和死信。Redis Pub/Sub 不保存消息,消费者离线时会丢失,不能直接作为关键任务总线。
- 长周期工作流:使用持久化工作流引擎或事件溯源状态机,支持 Checkpoint、补偿和恢复。
任务分发时,Planner 应输出结构化任务:task_id、父任务、依赖、输入 Artifact、能力标签、优先级、Deadline、预算、权限和验收条件。Router 根据 Agent 能力、负载、数据位置、风险等级和历史质量调度。Worker 通过租约领取任务,使用幂等键执行,并通过 Heartbeat 防止任务永久卡死;租约到期后可以重派,但必须用 Fencing Token 防止旧 Worker 迟到写入。
结果合并不能只依赖“多数投票”。多个 Agent 可能共享同一模型和同一错误,因此错误高度相关。更可靠的方法是按任务类型选择 Verifier:代码用测试和静态分析,检索用证据覆盖与引用,计算用程序复算,业务操作用环境状态和事务回执。对可拼接结果按 Schema 合并;对互斥结论保留证据、置信度和冲突原因,再由独立 Judge 或人工决策。
所有消息应至少带有 trace_id、task_id、attempt、schema_version、producer、时间、幂等键和 Artifact 引用,并限制 Agent 只能访问完成任务所需的工具与数据。
多 Agent 系统的价值不在于“Agent 数量更多”,而在于能否把任务分解、隔离执行、可靠传递和可验证合并做成一条闭环。
11. 上下文压缩保留什么信息?如何判断哪些内容可以丢弃?
面试官: 上下文压缩时保留什么信息?怎么判断哪些内容可以丢弃?
追问: 压缩任务交给小模型还是大模型?
回答:
上下文压缩应围绕“未来决策还依赖什么”来做,而不是围绕“哪些句子看起来重要”。建议保留以下几类信息:
- 当前目标与完成标准。 用户到底要什么,什么状态才算完成。
- 硬约束与授权。 时间、预算、格式、禁止项、权限范围、人工确认和安全规则。
- 任务状态。 已完成步骤、未完成步骤、阻塞原因、重试次数和下一步。
- 关键事实与实体。 明确确认的人、对象、数字、版本、路径、时间和业务状态。
- 决策及依据。 为什么选择某方案,哪些方案被排除,依据来自哪个证据或工具回执。
- 可恢复引用。 大型工具结果不必全文保留,但要保留 Artifact ID、查询条件、版本、摘要和关键片段。
- 用户稳定偏好。 只保留经用户确认、与后续任务相关且在隐私策略允许范围内的偏好。
通常可以压缩或丢弃的是:已被确定性结果取代的探索过程、重复日志、无新增信息的寒暄、已成功消费的大型中间输出,以及不影响后续行为的语言修辞。但不能简单删除所有“中间推理”。系统不需要保存模型私有 Chain-of-Thought,却需要保存可审计的决策摘要、证据和行动结果。
判断是否可丢弃,可以对每条信息做四个问题:后续步骤是否直接依赖它;删除后能否从外部 Artifact 可靠恢复;它是否属于安全或合规证据;它是否已被更新且旧值只需进入变更历史。压缩后应执行一致性校验,例如实体、数字、否定关系、未完成任务和权限条件是否仍然完整。
小模型还是大模型没有统一答案。结构化抽取、去重和日志归并可由规则或小模型完成;跨文档冲突、复杂计划和高风险事实压缩需要更强模型或双模型复核。选择标准应是压缩成本与下游错误成本,而不是模型价格本身。可以用事实保真率、约束召回率、未完成任务召回率、压缩率和最终任务成功率做评测。
优秀的上下文压缩不是写一段更短的摘要,而是构造一个可追溯、可更新、足以驱动下一步行动的最小任务状态。
12. LLM调用失败怎么排查?
面试官: 调用大模型API失败,你的排查流程是什么?
回答:
排查 LLM 调用失败应先回答三个问题:请求是否到达服务端,服务端是否开始生成,失败是否可以安全重试。推荐按调用链分层,而不是一上来只看 CPU 和内存。
- 确定影响范围。 是单请求、单模型、单地域、单租户还是全量失败;记录开始时间、错误率、P95/P99 延迟和最近变更。
- 建立关联。 用
trace_id/request_id串起网关、应用、模型服务和工具日志。记录模型、版本、区域、流式与否、输入/输出 Token、参数摘要和 HTTP 状态,但不得记录密钥或未脱敏的敏感 Prompt。 - 定位网络阶段。 分别检查 DNS、TCP、TLS、代理、连接池、连接超时、首字节/首 Token 超时、流式空闲超时和总 Deadline。能够建连不代表流式响应没有中途断开。
- 按状态码分类。
400多为参数、Schema 或上下文问题;401/403是认证、权限或项目配置;404可能是模型名或 Endpoint;409常见于状态冲突;429是速率或配额限制;5xx表示服务端、上游依赖或容量异常。仍应以具体供应商错误码为准。 - 检查请求内容。 计算真实 Token,验证模型上下文限制、Tool Schema、JSON 格式、图片大小、停止条件和内容安全策略。不要只按字符数猜 Token。
- 最小化复现。 使用相同模型和认证发送最小请求,再逐项恢复参数、工具和上下文,找出触发差异。对比最近成功样本与失败样本,但先做脱敏。
- 检查服务端资源。 若是自部署模型,再看队列长度、Batch、GPU OOM、KV Cache、显存碎片、推理引擎日志、模型权重、NCCL 和节点健康。
重试必须服从错误语义。连接重置、部分 429/5xx 可采用指数退避、随机抖动和 Retry-After;参数错误与权限错误不应重试。对有副作用的工具链要带幂等键,避免模型请求重试导致动作重复。流式调用中断后,不能默认把半截输出当成完整答案,应标记不完整并决定续写、重放或降级。
生产系统还需要熔断、备用模型、区域切换、队列削峰、请求限流和质量降级,并监控成功率、首 Token 延迟、生成吞吐、Token 使用、取消率和降级率。排障的关键是按层定位并保留请求关联,而不是把所有失败都归因于“网络波动”。
13. 分布式唯一ID怎么生成?要求自增怎么办?
面试官: 多台机器如何生成唯一ID?如果要求自增呢?
回答:
先要澄清“自增”的语义,因为唯一、趋势递增、单调递增、严格连续和按真实时间排序是不同要求,成本也完全不同。
- 只要求全局唯一:UUIDv4、随机 128 位 ID 等最简单,但索引局部性较差。
- 要求大致按时间排序:UUIDv7、ULID、Snowflake 类 ID 更适合数据库索引和日志排序。
- 要求每个业务分区单调递增:可以为租户、订单域或分片分别维护 Sequence。
- 要求全局严格递增:必须经过某个线性化的排序点,吞吐和可用性会受到约束。
- 要求无空洞连续:分布式系统中代价最高,事务回滚、预取和故障都会造成 Gap,通常不应把它作为普通主键需求。
Snowflake 一般由时间戳、节点 ID 和毫秒内序列组成,吞吐高、趋势递增且无需每次访问中心服务。风险包括时钟回拨、节点 ID 冲突、同毫秒序列耗尽和时间位溢出,因此需要时钟监控、回拨等待或逻辑时钟、节点租约与唯一分配。
如果要求全局严格递增,可以使用数据库 Sequence/自增主键、Redis INCR、具备共识的 ID 服务或单 Leader 排序服务。但这些方案都把生成过程集中到一个线性化点,需要高可用、持久化、故障切换和热点治理。Redis INCR 在单主正常工作时原子递增,但要结合持久化和故障切换语义评估是否可能回退或重复。
Leaf Segment 一类号段模式让应用批量领取区间,再在本地递增,显著降低中心压力;代价是趋势递增而非严格按请求时间排序,实例故障还会浪费未使用号段。多数据库设置不同起点和步长能保证不重复,却不能保证按生成时间严格单调,更不能保证无空洞。
数据库主键还要关注索引写入。完全随机 UUID 会让 B+ 树频繁页分裂;时间有序 ID 更利于追加写,但如果业务暴露连续 ID,又可能泄漏订单量并便于枚举,因此外部标识可与内部主键分离。
面试时最好的回答不是背三个算法,而是先把唯一性、顺序性、连续性、吞吐、可用性和信息泄漏要求拆开,再选择最低成本的满足方案。
14. 缓存击穿、穿透、雪崩是什么?怎么解决?
面试官: 缓存击穿、缓存穿透、缓存雪崩是什么?怎么解决?
回答:
三者的共同结果都是后端承压,但触发机制不同。
缓存穿透是请求的数据本来就不存在,缓存和数据库每次都 Miss。常见来源是恶意枚举、参数错误或脏数据。治理手段包括参数校验、鉴权与限流、缓存短 TTL 的空值、布隆过滤器或 Cuckoo Filter。过滤器存在误判且需要维护新增/删除一致性,不能替代数据库校验;空值缓存也要防止占满缓存。
缓存击穿是某个高热 Key 在失效或回源期间,大量并发同时访问数据库。常用方案有 SingleFlight/互斥回源、逻辑过期加异步刷新、提前刷新、热点 Key 永不过期但由版本切换更新,以及本地缓存兜底。互斥锁必须有超时、持有者标识和异常释放;等待线程要有 Deadline,不能无限阻塞。
缓存雪崩是大量 Key 在相近时间失效,或整个缓存集群、网络链路同时不可用,导致大规模流量回源。解决方案包括 TTL 随机抖动、分批预热、缓存高可用与分片、本地多级缓存、限流、熔断、请求排队和后端容量保护。需要注意,随机 TTL 只能缓解集中失效,不能解决 Redis 整体故障。
成熟系统还应处理缓存与数据库一致性。常见 Cache-Aside 流程是读缓存 Miss 后查库并回填,写操作先更新数据库再删除缓存,并通过消息、Binlog 或延迟双删处理失败窗口。对强一致业务,不能只靠“设置一个更短 TTL”;可以使用版本号、事务消息或直接绕开缓存。
压测时应模拟热点过期、随机不存在 Key、缓存节点宕机、网络抖动和回源慢查询,并观察数据库 QPS、连接池、缓存命中率、回源并发和恢复时间。穿透是不存在数据反复穿过,击穿是单个热点被打穿,雪崩是大面积缓存同时失守;治理重点分别是拦截无效请求、合并热点回源和保护整个后端。
15. Redis为什么能支撑高并发?
面试官: Redis为什么能支撑很高并发?
追问: Redis 6.0之后加了多线程IO,还能叫单线程吗?
回答:
Redis 高并发不是因为某一个“单线程黑科技”,而是数据路径短、事件模型简单、数据结构高效和批量化能力共同作用的结果。
首先,大多数访问在内存完成,避免了磁盘随机 I/O;但不能说网络请求是“纳秒级”,真实延迟还包含网络、协议解析、排队、持久化和主从复制,通常应以微秒到毫秒级服务指标衡量。
其次,Redis 使用事件循环和 I/O 多路复用管理大量连接,避免为每个连接创建一个阻塞线程。核心命令执行长期以串行语义为主,因此单条命令天然具有原子性,也减少了共享数据结构上的锁竞争和上下文切换。但耗时 $O(N)$ 命令、Lua 脚本或大 Key 仍会阻塞后续请求,所以“单线程”不等于任何场景都快。
再次,Redis 针对数据形态选择紧凑结构,例如 SDS、字典、Intset、Listpack、Quicklist 和 Skiplist,并结合渐进式 Rehash、过期策略和内存分配优化。Pipeline、MGET/MSET、连接复用和批量命令能够摊薄网络往返成本;集群分片则把压力扩展到多个节点。
Redis 6.0 之后引入多线程网络 I/O,后续版本也持续演进并行能力。因此现在更准确的说法是:Redis 不是一个所有工作都由单线程完成的程序;它可以使用后台线程和 I/O 线程,但大量核心命令仍保持每个分片内的串行执行语义。 网络读取、协议处理和写回可以并行,命令对核心键空间的执行模型仍强调确定性和低锁开销。
工程上还要关注高并发下的反例:大 Key 删除、全量扫描、复杂集合运算、Fork 抖动、AOF fsync、主从全量同步、热点 Key 和客户端输出缓冲区都可能成为瓶颈。优化顺序通常是先修正命令复杂度和数据模型,再用 Pipeline 和连接池降低 RTT,随后做热点拆分、读写分离或 Cluster 扩容。
Redis 能扛高并发的本质,是让绝大多数命令沿着短而可预测的数据路径执行;一旦命令复杂度、数据体量或持久化路径变得不可预测,单线程语义反而会放大尾延迟。
16. ThreadLocal为什么可能内存泄漏?
面试官: ThreadLocal为什么可能内存泄漏?
追问: 怎么避免?
回答:
每个 Java Thread 内部都可以持有一个 ThreadLocalMap。Map 中的 Entry 对 ThreadLocal Key 使用弱引用,但对 Value 使用强引用。当业务代码不再持有某个 ThreadLocal 时,Key 可能被 GC 回收并变成 null,而 Value 仍然被 Thread -> ThreadLocalMap -> Entry -> Value 这条强引用链持有。
如果线程很快结束,整个 Thread 和 Map 都可回收,问题不明显;在线程池、Web 容器和应用服务器中,Worker 线程长期存活,陈旧 Value 就可能长时间滞留,形成内存泄漏或至少是生命周期远超预期的内存占用。还要注意 ThreadLocalMap 是开放寻址的数组结构,不是普通的 Entry 链表。Java 会在 get/set/remove 等操作中机会性清理 Stale Entry,但不保证某个不再访问的槽位立即被清除。
标准写法是:
private static final ThreadLocal<Context> CONTEXT = new ThreadLocal<>();
try {
CONTEXT.set(context);
handleRequest();
} finally {
CONTEXT.remove();
}
避免问题需要做到:
- 每次
set()后在finally中remove(),即使异常或取消也要执行。 - 在线程池任务边界统一安装清理装饰器,避免业务开发者漏清理。
- 不在 ThreadLocal 中长期保存大对象、连接、ClassLoader、请求/用户对象或安全凭证。
- 在线程复用时防止上下文串号。即使没有 OOM,前一个请求的用户身份泄漏给后一个请求也是严重问题。
- 异步切线程时不要假设 ThreadLocal 自动传播。应显式传递 Context,或使用框架提供的受控上下文传播机制。
static final ThreadLocal 只能让 Key 自身长期存在,减少 Key 被提前回收的情况,却不能替代 remove();Value 仍会在线程之间残留。InheritableThreadLocal 在线程池中更容易产生错误继承,也要谨慎使用。
ThreadLocal 泄漏的根因不是弱引用本身,而是 Key 与 Value 生命周期不对称,再叠加长期存活线程;解决方案是把清理放到任务生命周期边界。
17. BeanFactory和ApplicationContext有什么差异?
面试官: BeanFactory和ApplicationContext有什么差异?
回答:
BeanFactory 是 Spring IoC 容器最基础的能力契约,负责 Bean 的注册、查找、依赖注入、作用域和生命周期协作。ApplicationContext 建立在更完整的 BeanFactory 体系之上,面向实际应用提供统一的运行时上下文。
二者的差异可以从四层理解:
- 容器能力。
ApplicationContext同时具备可列举和分层 BeanFactory 能力,并集成资源加载、环境与 Profile、国际化消息、应用事件发布等接口。 - 启动流程。 常见
ApplicationContext在refresh()时会自动发现并执行BeanFactoryPostProcessor、注册BeanPostProcessor,并默认预实例化非 Lazy 的 Singleton。裸BeanFactory通常需要调用者自行完成更多装配步骤。 - 基础设施集成。 AOP 自动代理、事务、注解处理、事件、组件扫描等能力通常由 Context 启动流程中的后处理器和基础设施 Bean 完整启用。
- 使用场景。 普通 Spring/Spring Boot 应用几乎都使用
ApplicationContext;BeanFactory更常作为底层 SPI、轻量测试或框架内部扩展点。
“BeanFactory 懒加载、ApplicationContext 饿加载”只是默认行为的简化说法,不是接口层面的绝对差异。Bean 是否延迟创建取决于 Bean 定义、作用域、Context 刷新策略和是否提前被依赖。ApplicationContext 也支持 @Lazy,BeanFactory 也可以主动预实例化 Singleton。
还应注意,不要把 ApplicationContext 理解成另一个与 BeanFactory 平行的容器。它仍以 BeanFactory 作为核心注册表和实例工厂,只是在其上增加应用级生命周期与服务。Spring Boot 又进一步负责创建和配置合适的 ApplicationContext,并加载自动配置。
一句话总结:BeanFactory 定义“Bean 怎么被管理”,ApplicationContext 定义“一个 Spring 应用如何在完整环境中启动、扩展和协作”。
18. 配置中心宕机了应用怎么不挂?
面试官: 如果配置中心宕机或连不上,怎样保证应用不挂?
回答:
配置中心容灾不能统一采用“失败就读本地默认值”。配置分为普通业务参数、动态开关、服务发现信息、密钥和安全策略,不同配置的 Fail-Open/Fail-Closed 策略应不同。旧的推荐排序可以继续使用,过期证书、撤销名单或权限策略却可能必须拒绝启动或进入只读模式。
完整方案包括:
- Last Known Good Snapshot。 客户端把最近一次校验通过的完整配置持久化到本地,包含版本、命名空间、时间、校验和或签名。启动连接失败时,只加载兼容且未超出允许陈旧时间的快照。
- 分级启动策略。 非关键配置可使用默认值或快照;关键安全配置缺失时 Fail-Fast;部分业务可进入只读或降级模式,避免带着未知权限继续写操作。
- 内存继续服务。 运行期间配置中心失联,已加载配置继续生效。后台以指数退避和随机抖动重连,避免配置中心恢复时所有实例同时重试形成惊群。
- 原子更新。 拉取新配置后先做 Schema、类型、范围、依赖和签名校验,再整体切换版本。不能逐字段更新,让请求读到半新半旧状态。
- 版本与回滚。 保留多个可用版本,若新配置导致错误率或启动失败,能够自动回退。配置变更要有审计、灰度和权限控制。
- 服务端高可用。 多节点、多可用区、持久化存储、客户端地址列表和健康检查,不能把全部可靠性寄托在客户端快照上。
本地 application.yml 可以提供非敏感的最小默认值,但不应硬编码生产密钥,也不能让默认值静默绕过安全限制。快照本身要设置文件权限、加密或签名校验,防止本地篡改。
验证时要做故障演练:冷启动时中心不可用、运行中断联、新配置损坏、版本回退、中心恢复后大规模重连,以及快照过期。观察应用是否启动、功能是否降级、配置是否一致和是否产生越权。
配置中心容灾的核心不是“永远不挂”,而是为每类配置明确可接受的陈旧程度和失败模式,让系统在依赖失效时仍保持可解释、可审计的行为。
19. TCP一定需要四次挥手吗?
面试官: TCP为什么有四次挥手?一定需要四次挥手吗?
回答:
TCP 是全双工协议,两个方向的字节流可以独立关闭。主动关闭方发送 FIN,只表示“我没有更多数据要发送”,并不表示对方也发送完了。被动关闭方先 ACK 这个 FIN,应用层可能还要继续发送剩余数据;等它也关闭发送方向时,再发自己的 FIN,最后由主动关闭方 ACK。因此逻辑上存在两个独立的半关闭过程:
A -> B: FIN
B -> A: ACK
B -> A: FIN
A -> B: ACK
但“四次挥手”描述的是四个逻辑报文动作,不保证线上一定看到四个独立 TCP Segment。如果 B 收到 FIN 后应用立即关闭,并且没有待发送数据,它对第一个 FIN 的 ACK 可以与自己的 FIN 合并在同一个 Segment 中,于是抓包可能表现为三次。关键条件是被动方何时进入关闭发送方向,而不是“开启延迟 ACK 就一定三次”。
其他情况也可能不同:双方同时主动关闭时会发生 Simultaneous Close;连接异常、进程强杀或设置特定 Socket 行为时可能通过 RST 中止,这不是正常的优雅关闭;中间设备和重传也会让抓包数量多于四个。
最后一个 ACK 之后,主动关闭方通常进入 TIME_WAIT,等待 $2\times\mathrm{MSL}$。原因是确保最后 ACK 丢失时还能重发,并让旧连接中的延迟报文在网络中消失,避免污染相同四元组的新连接。TIME_WAIT 不是“无用等待”,但在高并发短连接服务中需要通过连接复用、合理的端口与内核参数治理,而不是粗暴关闭协议保护。
FIN 也占用一个序列号,ACK 确认的是对方 FIN 的下一个序列号。收到 FIN 只关闭接收方向中的“对方继续发送”能力,本端仍可以发送数据,这正是 TCP Half-Close 的语义。
因此,TCP 关闭需要两个方向分别确认,但四个逻辑动作可以合并成更少的报文段;是否合并取决于应用关闭时机和协议栈行为。
20. 手撕:实现执行器,相同key串行、不同key并行
面试官: 实现一个执行器,相同key的任务串行执行,不同key的任务并行执行。
回答:
核心不变量有两个:同一个 Key 在任意时刻最多运行一个任务;不同 Key 的任务可以交给共享线程池并行运行。不能只用 ConcurrentHashMap<Key, Queue> 就认为线程安全,还要原子处理“入队、启动消费者、队列清空和删除 Key”之间的竞态,否则可能出现两个消费者同时运行,或新任务加入旧队列后无人继续消费。
下面的实现为每个 Key 建立一条 Lane。Lane 内使用短 synchronized 临界区保护队列和运行状态,实际任务在线程池中执行,不会持锁运行。Lane 关闭与 Map 删除之间通过 closed 和条件删除处理竞态。
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
public final class KeyedExecutor<K> {
private final ConcurrentHashMap<K, Lane> lanes = new ConcurrentHashMap<>();
private final Executor executor;
public KeyedExecutor(Executor executor) {
this.executor = Objects.requireNonNull(executor);
}
public CompletableFuture<Void> submit(K key, Runnable action) {
Objects.requireNonNull(key);
Objects.requireNonNull(action);
Task task = new Task(action);
for (;;) {
Lane lane = lanes.computeIfAbsent(key, Lane::new);
if (lane.enqueue(task)) {
return task.result;
}
// 该 Lane 已完成关闭,帮助移除后重试到新 Lane。
lanes.remove(key, lane);
}
}
private final class Lane {
private final K key;
private final ArrayDeque<Task> queue = new ArrayDeque<>();
private boolean running;
private boolean closed;
private Lane(K key) {
this.key = key;
}
boolean enqueue(Task task) {
boolean shouldSchedule = false;
synchronized (this) {
if (closed) {
return false;
}
queue.addLast(task);
if (!running) {
running = true;
shouldSchedule = true;
}
}
if (shouldSchedule) {
scheduleNext();
}
return true;
}
private void scheduleNext() {
try {
executor.execute(this::runOne);
} catch (RejectedExecutionException ex) {
failRemaining(ex);
}
}
private void runOne() {
Task task;
synchronized (this) {
task = queue.pollFirst();
if (task == null) {
running = false;
closed = true;
}
}
if (task == null) {
lanes.remove(key, this);
return;
}
if (!task.result.isCancelled()) {
try {
task.action.run();
task.result.complete(null);
} catch (Throwable ex) {
task.result.completeExceptionally(ex);
}
}
// 前一个任务完全结束后,才调度同 Key 的下一个任务。
scheduleNext();
}
private void failRemaining(Throwable cause) {
List<Task> pending;
synchronized (this) {
if (closed) {
return;
}
closed = true;
running = false;
pending = new ArrayList<>(queue);
queue.clear();
}
lanes.remove(key, this);
pending.forEach(task -> task.result.completeExceptionally(cause));
}
}
private static final class Task {
private final Runnable action;
private final CompletableFuture<Void> result = new CompletableFuture<>();
private Task(Runnable action) {
this.action = action;
}
}
}
复杂度方面,提交和出队平均为 $O(1)$;空间复杂度为 $O(K+N)$,其中 $K$ 是活跃 Key 数,$N$ 是排队任务数。单个 Key 的吞吐受串行约束,不同 Key 的并行度受底层 Executor 限制。
生产实现还应补充:每个 Key 的队列上限和背压;任务 Deadline 与取消;Metric 和 Trace;线程池关闭后的拒绝策略;热点 Key 公平性;Key 长期不释放的泄漏检查;以及是否允许前一个任务失败后继续执行后续任务。若任务有外部副作用,还要使用幂等键。
原始常见写法中,先发现队列为空,再分别删除 processing 和 queues,可能与并发 submit() 交错,造成任务落入即将被删除的旧队列。这道题真正考查的不是会不会使用并发容器,而是能否证明“每个 Key 只有一个消费者”并正确处理队列从空到非空、再从非空到空的状态转换。
本篇目录
当前问题0130
- 01
**面试官:** 有了解过KV Cache吗?它是什么作用,为什么能加速大模型推理?
- 02
**面试官追问:** 那KV Cache的代价是什么?长序列推理时显存怎么估算?
- 03
**面试官追问:** 那KV Cache的代价是什么?长序列推理时显存怎么估算?
- 04
**面试官:** Agent执行多步任务时,对话历史加工具返回结果很容易超限,你有什么处理方案?
- 05
**面试官追问:** 三种策略分别在什么场景下用?
- 06
**面试官:** 一个Agent任务耗时很长,用户等不及了,怎样停止这个任务?这个机制你实际尝试过吗?
- 07
**面试官:** 一个Agent任务耗时很长,用户等不及了,怎样停止这个任务?这个机制你实际尝试过吗?
- 08
**面试官:** MCP是什么?Agent通过MCP调用工具的流程是什么?
- 09
**面试官:** MCP是什么?Agent通过MCP调用工具的流程是什么?
- 10
**面试官追问:** 你觉得MCP能成为标准吗?
- 11
**面试官:** RAG为什么需要设计父子索引?检索小文本块、返回大父文档能解决什么问题?
- 12
**面试官追问:** 这样设计有什么代价?
- 13
**面试官:** ReAct模式和Plan模式有什么区别?分别适合什么场景?
- 14
**面试官:** Agent调用工具后,工具返回结果的消息结构怎么设计?为什么需要单独的Tool Message?
- 15
**面试官:** 多个Agent同时修改同一个文件,如何避免冲突?
- 16
**面试官:** Agent陷入死循环或重复调用同一个工具,你们的系统有什么兜底机制?
- 17
**面试官:** 多Agent之间怎么通信?如何做任务分发和结果合并?
- 18
**面试官:** 多Agent之间怎么通信?如何做任务分发和结果合并?
- 19
**面试官:** 上下文压缩时保留什么信息?怎么判断哪些内容可以丢弃?
- 20
**面试官:** 上下文压缩时保留什么信息?怎么判断哪些内容可以丢弃?
- 21
**面试官:** 调用大模型API失败,你的排查流程是什么?
- 22
**面试官:** 多台机器如何生成唯一ID?如果要求自增呢?
- 23
**面试官:** 多台机器如何生成唯一ID?如果要求自增呢?
- 24
**面试官:** 缓存击穿、缓存穿透、缓存雪崩是什么?怎么解决?
- 25
**面试官:** Redis为什么能支撑很高并发?
- 26
**面试官:** ThreadLocal为什么可能内存泄漏?
- 27
**面试官:** BeanFactory和ApplicationContext有什么差异?
- 28
**面试官:** 如果配置中心宕机或连不上,怎样保证应用不挂?
- 29
**面试官:** TCP为什么有四次挥手?一定需要四次挥手吗?
- 30
**面试官:** 实现一个执行器,相同key的任务串行执行,不同key的任务并行执行。
