12506 字
63 分钟
2026-11-12 AI Agent 面试训练
AI Agent 面试训练
2026-11-12

2026-11-12 AI Agent 面试训练#

今日学习主题:先找结构。两阶段提交的协议阶段、有序数组的单调性、Redis 的复制拓扑、文档中的表格结构、提示词的适用边界——每道题的公共主线是:当问题里存在可利用的结构时,先识别并利用它换确定性;结构不存在或不可依赖时,明确退路并给出概率路径的边界。

题目目录:

编号题目 ID类型一级类型二级主题题目名称难度一句话考察点
B01Q-20261112-B01非 Agent 基础分布式系统两阶段提交与分布式事务2PC 的承诺与阻塞窗口:prepare 之后协调者宕机了基础题(中高)两阶段提交保证什么、什么情况下阻塞、与 Saga 的取舍
B02Q-20261112-B02Agent 基础LLM 应用工程Prompt Engineering 的工程边界Prompt Engineering 能解决什么、不能解决什么基础题(中高)提示词改变的是条件分布的选择而非能力,何时应转微调或换模型
S01Q-20261112-S01Agent 场景RAG 与搜索文档解析、表格与结构化内容检索表格问答准确率 45%:结构化内容在 RAG 管道里的保真与检索场景题(高)解析保真度、表格索引粒度、引用锚点与解析降级
S02Q-20261112-S02非 Agent 场景数据库与存储主从复制、故障转移与数据丢失窗口主从切换后订单状态回退、锁被持有两次:异步复制的代价场景题(高)异步复制的丢失窗口、脑裂、min-replicas-to-write 与数据分级
L01Q-20261112-L01LeetCode双指针(对撞)有序数组的两数之和167. Two Sum II - Input Array Is Sortedeasy利用有序性把哈希表 O(n) 空间换成对撞双指针 O(1) 空间

配置读取状态:成功读取 题库配置.md,版本 v8(2026-11-02 维护)。无暂停类型;「观察中的新技术」均未标记「允许试学」,本轮不出题。13 个基础题类型与场景题类型全部启用,5 道固定配额不变。

去重结果:窗口为 2026-08-12 至 2026-11-12(运行日前推 3 个日历月),窗口内最早索引条目为 2026-10-10,无需剪除。对比题目索引 130 条记录:两阶段提交、Prompt Engineering 边界、表格类文档 RAG、Redis 主从复制与故障转移、LeetCode 167 均无同题、换皮题或同题号重复。参考的历史题目来源:题目索引.md(130 条)+ 2026-11-11 训练文件(主线衔接)。


B01 · 两阶段提交:prepare 之后协调者宕机了#

题目 ID:Q-20261112-B01 | 分类:基础(非 Agent) | 一级类型:分布式系统 | 二级主题:两阶段提交与分布式事务

题目#

你负责的交易系统中,跨订单库与库存库的扣减操作使用两阶段提交(2PC / XA)。某天协调者(事务管理器)进程在向两个参与者发送 COMMIT 决议之前崩溃,两个数据库各自持锁等待。请回答:两阶段提交到底向调用方承诺了什么?为什么此时两个参与者既不能提交也不能回滚?这个阻塞窗口如何解除?在什么条件下你应该放弃 2PC 改用 Saga 或最终一致?

先思考#

  1. 第一阶段结束时,每个参与者已经做出了什么不可撤销的承诺?这个承诺意味着它持有什么资源?
  2. 协调者恢复后,它如何知道有哪些事务处于”准备完成但决议未传达”的状态?参与者侧能看到什么线索?
  3. 2PC 的阻塞窗口长度由什么决定?它与”参与者也宕机”叠加时会发生什么?
  4. 对比 Saga:两者的原子性承诺分别到什么粒度?中间状态对外可见性差别在哪?

参考答案#

两阶段提交是一个原子提交协议:它向调用方承诺”要么所有参与者提交、要么所有参与者回滚”,前提是协调者最终恢复并完成决议传达。它不是一致性协议,也不提供隔离性以外的任何并发保护——隔离仍由各参与者本地事务承担。

适用边界:2PC 适合同步、低延迟、协调者可靠、事务持有时间短的跨资源原子提交(如同一机房的两个数据库、消息系统与数据库的原子写入);不适合跨广域网、长事务、协调者可能长期不可用的场景。PostgreSQL 官方文档明确说明 PREPARE TRANSACTION 是给外部事务管理器用的,不建议应用直接使用,且长期处于 prepared 状态会阻碍 VACUUM 回收、极端情况下引发 XID 回卷停机——这是”阻塞窗口有真实运维代价”的一手佐证。

具体示例:订单库执行 UPDATE orders SET status='paid' 后收到 PREPARE TRANSACTION 'tx_991'。此刻订单库已把这行修改连同其行锁写入磁盘(事务状态完全持久化),但对外不可见;它唯一能做的,是等协调者传达 COMMIT PREPARED 或 ROLLBACK PREPARED。协调者此刻宕机,这两个决定权就”悬”在日志里——这就是 in-doubt(不确定)事务。

与相近概念的区别:3PC 在 2PC 之间插入预提交阶段以缩小阻塞窗口,但引入更多轮通信且在网络分区下反而可能出现两个协调者,工业界极少使用;Saga 把长事务拆成一串本地事务加补偿,中间状态对外可见,用业务补偿而非协议保证原子性;共识协议(Raft/Paxos)解决的是”多副本对同一决议达成一致”,2PC 解决的是”多个独立资源对同一事务同生共死”,二者经常组合使用(协调者用共识组做高可用),但不能互相替代。

典型工程应用:XA/JTA 事务管理器、PostgreSQL max_prepared_transactions、Kafka 事务协调器(producer 端跨分区的原子写)、Seata AT 模式的全局锁设计。在 Agent 系统之外,它是”确定性系统能提供强原子承诺”的边界样本——面试中可以顺势指出:模型和 Agent 编排永远给不了这种承诺,涉及钱的副作用必须落在这类确定性协议或其补偿等价物上。

面试现场回答:先给结论——2PC 承诺的是跨资源原子提交,代价是prepare 之后所有参与者持锁等待协调者决议,协调者宕机即全局阻塞;解除靠协调者恢复后从其日志读出决议并完成 COMMIT/ROLLBACK PREPARED,若协调者日志也丢了则只能人工裁决每个 in-doubt 事务(通常按业务规则回滚);当事务持有时间长、协调者可用性无法保证、或阻塞窗口的业务代价超过”中间状态可见 + 补偿”的代价时,改用 Saga,把一致性从协议层移到业务层。

追问#

追问:协调者宕机时,如果某个参与者也恰好宕机,恢复后会发生什么?

回答要点:这是 2PC 最著名的失效组合——其他参与者既不知道该事务最终提交与否,也拿不到任何决议(决议只存在于协调者与宕机参与者的预期中),这些参与者上的锁会一直持有到协调者恢复。如果协调者日志缺失或无法恢复,只能人工按业务规则对每个 prepared 事务做裁决,且裁决必须全局一致(要么全提交要么全回滚,不能一半一半)。这也解释了为什么实践中 2PC 协调者必须运行在共识组或至少有日志备份的高可用基座上——2PC 的可用性上限就是协调者的可用性上限。

关键点#

  1. prepare 是不可撤销的承诺:参与者持久化修改并持有锁,只等决议,这是阻塞的根源。
  2. 阻塞窗口的长度由协调者的恢复时间决定,而不是由网络决定;协调者日志是决议的唯一权威来源。
  3. 2PC 保证原子性不保证可用性:任何一个参与者的故障都会冻结整个事务。
  4. 长期 prepared 事务有真实运维代价(锁、VACUUM、XID 回卷),PostgreSQL 默认 max_prepared_transactions=0 正是这个原因。
  5. 2PC 与 Saga 的选择本质是”把一致性代价放在协议层(锁 + 阻塞)还是业务层(中间状态可见 + 补偿)”。

常见误区#

  1. 把 2PC 当成分布式锁或一致性协议:它只对单个事务的提交决议原子化,不防止两个 2PC 事务之间的并发冲突,也不提供跨副本一致性。
  2. 认为协调者宕机会自动回滚:不会。没有决议之前,参与者没有权限单方面放弃 prepare 承诺,否则就破坏了原子性(可能出现”部分提交”)。
  3. 用 2PC 承载长事务或跨机房事务:锁定窗口随事务时长线性放大,跨机房 RTT 使每个阶段都变慢且协调者故障恢复更难;这类场景 Saga 是更合理的默认。

延伸学习#

  1. PostgreSQL 官方文档 PREPARE TRANSACTION(含 in-doubt 事务处置与 max_prepared_transactions 的告诫):https://www.postgresql.org/docs/current/sql-prepare-transaction.html (已核验)
  2. Anthropic 之外的通用阅读方向:Kafka 官方文档 “Transactions in Apache Kafka” 一节,观察 2PC 思想在消息系统的具体化与协调器角色的落点。(方向性推荐,未逐字核验)

B02 · Prompt Engineering 能解决什么、不能解决什么#

题目 ID:Q-20261112-B02 | 分类:基础(Agent) | 一级类型:LLM 应用工程 | 二级主题:Prompt Engineering 的工程边界

题目#

定义 Prompt Engineering 作为一项工程方法:它通过什么机制影响模型输出?它的适用边界在哪里——哪些失败可以通过改提示词修复,哪些不能?它与微调、RAG、上下文工程分别是什么分工?

先思考#

  1. 模型的权重在推理时不变,那么提示词改变的是数学上的什么?
  2. “少样本示例”提供的是知识还是模式?如果任务需要模型不知道的事实,提示词救得了吗?
  3. 同一段指令换一种措辞输出就明显变化,这说明了提示词的什么性质?这对版本管理意味着什么?
  4. 官方文档里”不是所有失败评测都该靠提示词工程解决”指的是哪类问题?

参考答案#

定义:Prompt Engineering 是在不改变模型权重的前提下,通过组织输入(指令、示例、格式约定、上下文材料)来选择和引导模型条件分布的工程方法。它改变的是”给定这些输入时,模型倾向于生成什么”,而不是模型”会什么”。

适用边界:它适合修复行为与表达层的问题——输出格式不合规、语气不对、任务定义不清、示例缺失导致的模式偏差、缺少推理引导;它不能修复能力与知识层的问题——模型没学过的事实(该走 RAG)、系统性的格式遵循缺陷(该走微调或换模型)、延迟与成本问题(官方文档明确指出这类问题换模型往往更直接)、以及对确定性结果的强承诺(任何提示词都给不了)。另一个硬边界是敏感性:措辞、示例顺序、示例位置的微小变化都会引起输出分布的可见波动,所以提示词是必须版本化、绑定评测集、按模型版本锁定的部署物,而不是一段”写完就稳定”的文本。

简洁示例:客服分类任务中,给 3 个带标签的边界样本(其中 1 个是易混淆负例),通常比堆 500 字指令更能压低混淆率——因为示例在上下文内提供了任务模式的锚点;但如果用户问题里包含产品手册之外的新政策,再好的提示词也只能让模型编一个像样的答案,此时正确动作是接 RAG。

与相近概念的区别:微调把模式固化进权重,适合高吞吐、任务单一、上下文成本敏感的场景,但知识注入仍不可靠且更新滞后;RAG 解决知识新鲜度与私有知识,不解决行为遵循;上下文工程是更大的系统级方法——决定哪些信息在什么时候进入上下文(含提示缓存布局),Prompt Engineering 是其中”组织这些信息”的那一层。面试时可以说:提示词是上下文的编排格式,上下文工程决定放进什么,微调决定权重默认倾向,RAG 决定事实从哪来。

在 Agent 系统中的典型应用:系统提示词承担任务定义、工具选择的指引(“涉及余额查询优先用 get_balance 而不是搜索”)、输出格式约定与失败时的重试指令;但 Agent 的硬约束不能只放在提示词里——授权、预算、危险操作的闸门必须在模型之外的确定性层执行,提示词只能降低越权概率,不能消灭它。

面试现场回答:Prompt Engineering 通过组织输入选择模型的输出分布,适合格式、行为、任务定义类问题,不适合知识缺失(走 RAG)、系统性能力缺陷(走微调或换模型)、延迟成本(换模型)这类问题;它对措辞敏感、随模型版本漂移,所以必须版本化并与评测绑定;在 Agent 里它是指引层而非约束层,硬约束永远外置。

追问#

追问:你怎么判断一个失败该用提示词修,还是该换手段?

回答要点:先问失败是”分布没选对”还是”分布里根本没有正确答案”:把同一个输入采样 N 次,若正确答案偶尔出现,说明能力在、引导不够,先调提示词(清晰化任务、补边界示例、加格式约定);若 N 次全错或错误模式一致且与措辞无关,提示词大概率无效,按问题类型分流——缺知识接 RAG、行为系统性偏差考虑微调、延迟成本换模型。再用消融评测确认:改提示词后指标变化必须超出采样方差,否则是噪声。

关键点#

  1. 提示词改变的是条件分布的选择,不注入新知识、不固化能力。
  2. 敏感性是工程属性不是瑕疵:提示词必须版本化、绑定评测集、随模型版本一起锁定。
  3. 四种手段的分工:提示词(行为引导)、RAG(知识供给)、微调(权重倾向)、换模型(能力与成本边界)。
  4. 少样本示例提供的是模式锚点,选边界样本(含易混淆负例)比堆数量更有效。
  5. Agent 中提示词是指引不是闸门;硬约束外置到确定性层。

常见误区#

  1. 把提示词当确定性保证:即使指令写了”必须、绝对不允许”,采样机制下仍可能违反;需要保证的地方必须有代码级校验。
  2. 用提示词注入知识:把整本手册塞进提示词替代 RAG,会同时吃掉上下文预算和提示缓存命中,且长上下文中间部分遵循度下降;知识供给是 RAG 的职责。
  3. 提示词改动不做消融评测:凭单次运行判断”变好了”,实际上没有超出采样方差;提示词变更应与代码变更同等级别对待(评审、评测、灰度)。

延伸学习#

  1. Anthropic Prompt Engineering Overview(官方技巧分类与”不是所有失败都该靠提示词解决”的原文表述):https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview (已核验,2026-11-12 经重定向跳转确认)
  2. OpenAI Prompt Engineering Guide(官方对应方向):https://platform.openai.com/docs/guides/prompt-engineering (方向性推荐,本轮未逐字核验)

S01 · 表格问答准确率 45%:结构化内容在 RAG 管道里的保真与检索#

题目 ID:Q-20261112-S01 | 分类:场景(Agent) | 一级类型:RAG 与搜索(兼属上下文工程与能力封装) | 二级主题:文档解析、表格与结构化内容检索

题目#

业务背景:企业内部客服 Agent,基于 50 万份 PDF(合同、费率表、配额制度)做 RAG 问答,日问答量 3 万次。当前架构:PDF → 版面解析 → 固定 512 token 切片 → bge 类 embedding → 向量库 + BM25 混合检索 → cross-encoder 重排 → 生成,引用带页码。

已知现象:纯文本问题(条款解释、流程说明)答案准确率 85%;涉及表格的问题(某档位费率、某区域配额、对比两列数值)准确率只有 45%,且错误模式集中:检索命中的是表格附近的文字段落,生成时模型把相邻行的数值张冠李戴,引用页码正确但具体到表内行列时经常错。

目标与约束:三个月内把表格类问题准确率提到 75% 以上;不能停服重建全量索引;解析与索引预算有限——多模态模型逐页读图的单页成本是文本解析的 30 倍,只允许对高风险文档子集使用;引用必须可追溯(客服需要给用户指出”这是第 X 页表格第 Y 行”)。

需要你给出排查思路、改造方案,并明确一个关键取舍:表格检索是”整表作为一个检索单元 + 生成时整表进入上下文”,还是”行级拆分后按行检索”?

先思考#

  1. 45% 的错误发生在哪几层:解析(表格根本没被结构化地读出来)、切片(结构被切断)、检索(命不中表)、还是生成(命中了但读错行列)?每层各怎么用小样本验证?
  2. 固定 512 token 切片对一张 40 行费率表意味着什么?表头和”第 12 档”那一行还会在同一片里吗?
  3. embedding 对表格文本的表示能力如何?“3 档 | 0.8% | 华东”这样的 token 序列,语义上接近的是什么?
  4. “行级检索”解决了命中,但生成端拿到了什么?没有表头和列语义的裸行能回答”对比”类问题吗?
  5. 你的引用要求(第 X 页第 Y 行)对解析产物提出了什么额外要求?

参考答案#

结论先行:这不是检索参数问题,而是解析保真度与索引粒度双双丢失了表格结构——先分层归因,再按”表格感知解析 + 双层索引(表级摘要块 + 行级块)+ 生成端整表供给”改造,粒度取舍取双层而不是二选一。

问题拆解:第一层归因(解析):抽样 100 份含表格文档,对比解析产物与原 PDF——常见失效是表格被线性化成无分隔的文本流或干脆丢失单元格边界;这一层用”解析产物能否重建表格”直接判分。第二层归因(切片):512 token 切片会把宽表纵向切断,表头(列名)与数据行分离,行块失去列语义——“0.8%“不再知道自己对应”第三档费率”。第三层归因(检索):表格文本的稠密向量表示很弱(数值与符号没有语义),BM25 对”华东第三档费率”这类查询也常命中周围的说明文字而非表体。第四层归因(生成):即使命中,切片里只有半张表,模型只能脑补行列对应关系。

核心方案:解析层,表格区域用结构感知解析器输出带行列坐标的结构化表示(HTML/markdown + 页码 + 行号),多模态逐页读图只留给解析失败率高的扫描件子集(按风险分级控制成本);切片层,表格不再进 512 token 通用切片,改为双层索引——表级摘要块(表题 + 表头 + 列说明 + 摘要行,人工或模型生成)负责召回,行级块(每行带继承的表头上下文,如”费率表·华东区·第三档:0.8%“)负责精确命中;检索层,行级块走混合检索,表级块保证”存在一张相关表”这一信号不丢;生成层,一旦召回命中某张表,无论命中的是行块还是表块,都把整张表(宽表超出预算时给核心列 + 命中行上下邻行)注入上下文,让模型在完整结构上做对比和取值,而不是在半张表上推理。

执行流程:入库时解析 → 表格检测 → 结构化抽取 → 双层块生成(行块 ID 含 doc_id/page/table_id/row_id)→ 分别入索引;查询时检索 → 命中行块则按 table_id 取整表 → 组装上下文 → 生成;引用锚点直接由 row_id 推导出”第 X 页表 Y 第 Z 行”,满足可追溯要求。

技术选型与备选:结构感知解析(html 输出的版面解析库)vs 纯多模态直读——后者保真度上限高但成本 30 倍且引入视觉幻觉的新失败模式,按文档风险分级使用;行级拆分 vs 整表索引——只整表则长表检索不中(嵌入稀释),只行级则生成端缺结构,双层 + 整表供给兼顾两端;重排模型对表格块作用有限(它也在文本表示上打分),不应把希望寄托在调重排上。

方案取舍:准确率 vs 成本——多模态解析按风险子集启用,保住预算;召回粒度 vs 上下文预算——整表供给设 token 上限,超限先裁列再裁行并保留命中行邻域;重建 vs 增量——不能停服,走”解析版本 v2”字段:新解析只对新文档与高风险文档生效,旧文档按后台队列渐进重解析,查询时同一文档只读一个表示版本(新旧不混,吸取 2026-11-10 Embedding 迁移一题的教训)。

失败路径与边界条件:解析失败的表格降级为”原文区域截图 + OCR 文本”存证并打标,该文档的表格问题走”已识别无法结构化解析,建议人工查看原件”的诚实输出,不硬答;解析置信度低的表打低质量标记,检索端降权;整表注入超预算时的裁剪顺序是确定性代码而非模型决定。

超时、重试、幂等与恢复:重解析队列按 doc_id + parser_version 幂等,失败退避重试三次后进人工队列;解析器超时不阻塞入库主链路,先用旧版本产物兜底;渐进重解析记录位点(按 doc_id 游标),中断续跑。

安全、成本、延迟、可靠性:表格摘要生成若用模型,输出需校验(不得引入表外数值,可用”摘要中的数值必须能在原表中找到”做断言);成本上多模态解析按日配额限流;延迟上整表注入使生成输入变长,需监控 TTFT 与提示缓存命中(表块放上下文尾部动态区,系统指令区保持稳定前缀)。

评测指标与观测:建”表格问答评测集”(按错误模式分层:取值 / 跨行对比 / 跨表对比,各 ≥50 条),分层指标——解析重建率、行块召回率、整表命中率、答案单元格级准确率、引用精确到行列的比例;上线后按问题类型分桶监控准确率,观测”引用了表格但引用行列错误”作为残余风险信号。

面试官追问及要点见下。

追问#

追问 1:为什么固定切片对表格是灾难?有没有”调大切片”这类轻量修复?

回答要点:固定切片按 token 均匀切割,不感知结构边界,宽表会被纵向切断且表头必然与多数数据行分离,行块失去列语义——这是”0.8%“被张冠李戴到别的列的直接原因。调大切片(如 2048)只是让切断频率降低,不能保证边界对齐,且稀释 embedding 表示、加剧 2026-10-16 已讨论过的”切片切断前提与结论”问题;正确修复是结构感知的切分(表格按结构单元切,文本按语义单元切),切片大小是参数,结构边界是前提。

追问 2:如何评测表格问答,和普通 RAG 评测有什么不同?

回答要点:普通 RAG 评测看答案忠实与检索命中,表格问答要下钻到单元格粒度:答案中的每个数值/结论应能定位到 table_id/row_id/col,评测集按错误模式分层(取值、对比、跨表),指标含解析重建率(结构化产物与原表逐格比对)、行块召回、答案单元格级准确率、引用行列精确率;无 reference 的通用 faithfulness 指标对”数值张冠李戴”几乎无感——模型可以忠实于错误上下文,所以必须有单元格级断言。

追问 3:如果直接换成多模态模型读原页面图片回答,跳过解析管道,行不行?

回答要点:可行但要算三笔账——成本(30 倍单页成本 × 3 万次日问),必须先检索定位页面再直读,检索端问题并未消失;可靠性(视觉幻觉、行列对齐错误是新的失败模式,需要单元格级校验兜底);延迟(多模态推理更慢)。合理形态是混合:文本管道为主、多模态直读作为解析失败或高价值问题的升级通道,且两条路径的引用格式必须统一。

关键点#

  1. 分层归因先行:解析、切片、检索、生成四层的失效模式不同,修复动作完全不同,用小样本抽样归因而不是直接调参。
  2. 表格的召回与生成粒度应该分离:表级摘要块管召回,行级块管精确命中,生成端供给整表。
  3. 行块必须继承表头上下文,否则单元格数值失去列语义。
  4. 引用锚点(页/表/行)是解析产物的结构化字段,不是生成后补的。
  5. 解析失败走诚实降级(不可结构化 → 建议人工),不用模型硬答。

常见误区#

  1. 把表格问题当成检索调参问题:调 top-k、换重排模型都不解决”行块缺列语义”与”表被切断”这两个结构损伤,指标会原地踏步。
  2. 行级拆分后直接生成:检索命中率上去了,但生成端拿到的是无表头裸行,对比类问题照样错;召回粒度和生成供给粒度必须分开设计。
  3. 新旧解析产物混存:同一文档一半切片来自旧解析一半来自新解析,引用锚点体系互相冲突;解析迁移必须按文档粒度整体切换并绑定版本。
  4. 引用只精确到页:页码正确、行列错误正是本次故障现象的一部分,把”引用精确率”只统计到页级会掩盖残余缺陷。

延伸学习#

  1. Unstructured 官方文档 partition(表格与版面结构化解析的工程实践方向):https://docs.unstructured.io/ (方向性推荐,本轮未逐字核验具体页面)
  2. Anthropic Docs:多模态与视觉输入指南(多模态直读路径的官方边界说明):https://platform.claude.com/docs/en/build-with-claude/vision (方向性推荐,未核验)

小型实践#

取 1 份真实费率表 PDF(含 ≥20 行 × ≥5 列):① 用当前管道解析并切片,手工标注每个切片的行列损伤;② 用任一结构感知解析器(如 html 输出的版面解析库)重做解析产物,对比单元格级重建率;③ 构造 10 个取值/对比类问题,分别用”旧切片直接检索生成”与”行块命中后注入整表”两条路径跑,对比答案单元格级准确率与引用行列精确率。全程 60 分钟内可完成。


S02 · 主从切换后订单状态回退、锁被持有两次:异步复制的代价#

题目 ID:Q-20261112-S02 | 分类:场景(非 Agent) | 一级类型:数据库与存储(兼属分布式系统) | 二级主题:主从复制、故障转移与数据丢失窗口

题目#

业务背景:电商平台的订单状态缓存与库存扣减分布式锁都在 Redis 上,部署为 12 个分片,每分片 1 主 2 从,Sentinel 自动故障转移,写 QPS 峰值 8 万。

当前架构与数据流:订单服务写入 Redis(订单状态 hash、库存扣减锁 SET NX PX),读走主库,从库仅做容灾备份;Redis 持久化 RDB 每小时 + AOF everysec;Sentinel down-after-milliseconds=5s,故障转移约 10 秒完成。

故障现象:一次主库硬件故障自动切换后出现两类问题:① 部分已确认的订单状态回退——客户端收到写成功,但新主上没有这笔写入;② 库存扣减锁被两个客户端同时持有——客户端 A 在旧主上拿到锁并写入,故障转移后旧主降级为从,其上未同步的锁键丢失,客户端 B 在新主上再次拿到同一把锁,发生超卖。

目标与约束:不更换 Redis 集群形态(业务仍需 Redis 的延迟),但必须消除”确认成功却丢失”和”锁双持”这两类事故;允许引入新组件;切换期间写入可用性损失要可控(不能像现在这样静默,但也不能长时间拒绝服务)。

请给出根因分析、改造方案,并明确取舍:用 min-replicas-to-write 这类”有界丢失”机制,还是把锁与状态类写入挪出 Redis?

先思考#

  1. Redis 官方文档对复制模式的表述是什么?“异步复制 + 每秒一次的副本确认”意味着确认成功的写与副本实际收到的写之间有什么关系?
  2. WAIT 命令能把 Redis 变成 CP 系统吗?为什么文档明确说不能?
  3. 故障转移期间,旧主短暂恢复在线时会发生什么?它还是主吗?
  4. 分布式锁的正确性应该由”锁在 Redis 上不丢”保证,还是由”拿到锁的执行者被资源侧校验”保证?
  5. 你的数据里哪些真的容忍丢失(缓存),哪些不容忍(状态、锁)?分级的依据是什么?

参考答案#

结论先行:根因是把不容忍丢失的写入放在了默认异步复制的存储上,又把锁的正确性寄托在”锁不会丢”这个 Redis 不提供的承诺上。修复不是把 Redis 调成强一致(做不到),而是按数据分级:缓存留在 Redis 接受有界丢失,状态类写入改走同步复制的主存储,锁改用”fencing token + 资源侧校验”的正确性模型。

问题拆解:① 订单状态回退——Redis 官方文档明确:复制默认异步,副本每秒向主确认处理进度,主不等待副本,因此已确认的写完全可能在故障转移时丢失,WAIT 也只是把丢失概率压到难触发的失效模式,文档原话是它”不会把一组 Redis 实例变成强一致的 CP 系统”。② 锁双持——SET NX PX 的互斥前提是”锁键持续存在”,但锁键本身也是一条可丢失的写入;转移期间旧主恢复后会降级为从并被新主数据覆盖,其上未同步的锁消失,第二个客户端自然能拿到锁。这是”锁定窗口内数据丢失”对互斥语义的直接破坏,与分布式锁的经典失效分析一致。

核心方案:数据分级——订单状态 hash 从 Redis 移到主存储(PostgreSQL,同步提交),Redis 只保留为读缓存,写路径”先库后缓存”,缓存允许短暂陈旧但不承载真值;库存扣减这类必须互斥的操作,正确性模型改为:Redis 锁只做效率优化(减少并发争抢),正确性由资源侧 fencing token 保证——锁服务发单调递增 token,库存数据库侧校验”携带最大 token 的请求才可执行”,即使两把锁并存,旧 token 的写入会被资源侧拒绝,超卖不再可能。此模型与 2026-10-21 分布式锁一题的”fencing token 须由被保护资源侧校验”结论一致。

执行流程:状态写入:业务事务(PG)提交 → 变更事件写 Redis 缓存(TTL + 事件驱动失效)→ 读路径缓存 miss 回源;锁获取:Redis SET NX PX + token 发号器取单调 token → 执行携带 token → 资源侧校验 token ≥ 已见最大值才生效;故障转移后旧锁丢失只影响效率(可能重复争抢),不影响正确性。

技术选型与备选:min-replicas-to-write=1 + min-replicas-max-lag=2——官方文档定位它是”best effort 数据安全机制”,把丢失窗口从无界收窄到约 lag 上界秒级,代价是副本滞后超限时主拒绝写入(可用性换一致性);保留它作为缓存分片的兜底,但明确它不解决锁双持(转移瞬间的窗口内锁键仍可能丢)。备选 A:锁改用支持同步复制的存储(如 PG advisory lock)——一致性最强但引入主库压力与延迟;备选 B:多节点锁(Redlock)——它同样被指出在时钟跳变与长时间 GC 暂停下不提供理想互斥,且增加复杂度,fencing token 路线更简单且正确性锚点清晰。

方案取舍:强一致 vs 可用性——min-replicas-to-write 用写入可用性换有界丢失,适合缓存类;状态与锁走”资源侧校验”模型,把一致性代价移出 Redis。延迟 vs 正确性——状态写路径多一次 PG 提交,写延迟上升约几毫秒,用”先库后缓存、缓存异步回填读路径”控制对读延迟的影响。

失败路径、边界、超时、重试、幂等与恢复:PG 写失败则业务失败,无”Redis 成功但真值丢失”的状态;缓存回填失败只降级命中率;锁的 token 发号器需高可用(可用 PG sequence 承载,与资源同源,避免第二个单点);资源侧校验需原子(UPDATE ... WHERE token > seen_token 的 CAS 语义,配合唯一约束幂等);转移窗口内重复拿锁导致的重复执行由资源侧 CAS 自然拒绝,无需补偿。

性能、可用性与验证指标:写延迟预算 PG 提交 P99 < 10ms,读路径缓存命中不变;写入可用性:缓存分片在 min-replicas-to-write 拒写时返回明确错误(客户端降级读源),状态分片随 PG 可用性走;验证指标——故障演练中”确认成功写入的丢失数 = 0”(状态类)、“资源侧拒绝的旧 token 写入数 > 0”(证明 fencing 生效)、锁重获率(效率指标,可容忍);季度级混沌演练覆盖”主库 + Sentinel 同时故障”的边界。

面试官追问及要点见下。

追问#

追问 1:为什么 Redis 的复制模式决定了它给不了强一致?

回答要点:官方文档明确三点:默认异步复制、副本确认是每秒一次的进度汇报而非逐写确认、WAIT 只保证”指定数量副本已确认接收”却不改变故障转移时已确认写入仍可能丢失的事实(取决于持久化配置)。强一致要求提交决议在多数派持久化之后才对客户端可见,Redis 的设计目标是低延迟高性能,整体架构不满足这个前提;min-replicas-to-write 系列只能把丢失窗口从无界变为有界(约 lag 上限秒级),量变不是质变。

追问 2:加了 min-replicas-to-write 后,为什么锁双持依然可能?

回答要点:该机制在写入时校验副本活跃度,只收窄写入丢失的时间窗口,不消灭窗口;故障转移的瞬间(旧主宕机到 Sentinel 完成提升的秒级区间)锁键仍可能处于”已确认未同步”状态,B 客户端仍可拿到同一把锁。互斥语义的正确性必须落在资源侧 fencing 校验上,写入窗口收窄只是减少 fencing 拒绝的发生频率,两者不可互相替代。

追问 3:如果要彻底移除对 Redis 锁的依赖,你会怎么迁移?

回答要点:分两步——第一步先落资源侧 fencing 校验(不动锁本身,正确性立即闭环,此步上线即可宣称消除超卖);第二步把锁获取迁到与资源同源的服务(PG advisory lock 或租约表),Redis 锁降级为纯缓存层的并发整形,最后摘除。迁移期两套锁并存时以 fencing token 为唯一正确性判据,避免”双锁都认为对方是优化”的责任真空。

关键点#

  1. 异步复制下”写确认成功”与”副本收到”没有蕴含关系;WAIT 与 min-replicas-to-write 都只能有界化丢失,不能提供强一致。
  2. 键值存储上的分布式锁,其互斥前提(锁键不丢)恰恰是该存储不承诺的;正确性必须移到资源侧 fencing token。
  3. 数据分级是第一决策:缓存容忍有界丢失留在 Redis,真值与互斥离开 Redis。
  4. 故障转移期间旧主的短暂回归是双写窗口的来源,fencing 使其无害。
  5. 官方文档对能力边界的表述(best effort / not CP)应作为设计输入,而不是被忽视的脚注。

常见误区#

  1. 认为”主从 + 自动切换”等于不丢数据:切换解决可用性,复制模式才决定丢失窗口;本次事故正是把这两件事混为一谈。
  2. 用更长的锁 TTL 修双持:TTL 只影响锁的自动释放时机,与”锁键丢失导致第二把锁”无关,加大 TTL 反而延长真故障时其他客户端的等待。
  3. 把 Redlock 当作强互斥认证:多节点锁在时钟跳变、进程暂停下同样有失效模式,且不解决”确认写入丢失”这个根因;它可以是效率层选项,不能是正确性锚点。

延伸学习#

  1. Redis 官方文档 Replication(异步复制、WAIT 的边界、min-replicas-to-write 的 best effort 定位):https://redis.io/docs/latest/operate/oss_and_stack/management/replication/ (已核验)
  2. Martin Kleppmann, “How to do distributed locking”(fencing token 论证的经典文章,与 Redis 官方回复对照阅读):https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html (方向性推荐,本轮未重新逐字核验)

小型实践#

本地起 1 主 2 从 Redis(docker-compose):① 关闭主库模拟故障,手动 promote 一个从库,观察原主恢复后的降级行为;② 写一个脚本循环 INCR 计数键,故障切换后对比客户端收到的确认次数与新主上的键值,量化丢失窗口;③ 给该键加 min-replicas-to-write 1 + min-replicas-max-lag 1,重复实验,对比丢失量与拒写时长。60 分钟内可完成。


L01 · LeetCode 167. 两数之和 II - 输入有序数组#

题目 ID:Q-20261112-L01 | 分类:LeetCode | 算法模式:双指针(对撞)| 难度:easy

题目#

LeetCode 题号:167 | 中文名:两数之和 II - 输入有序数组 | 英文名:Two Sum II - Input Array Is Sorted | 难度:Easy 官方链接(已核验,2026-11-12):https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/

题目描述:给定一个下标从 1 开始、已按非递减顺序排列的整数数组 numbers,找出和为目标值 target 的两个数,返回它们的下标 [index1, index2](index1 < index2)。

输入输出:输入有序数组与整数 target;输出长度 2 的下标数组(1-based)。

约束:

  • 2 <= numbers.length <= 3 * 10^4
  • -1000 <= numbers[i] <= 1000
  • numbers 按非递减顺序排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案;不可以重复使用同一元素
  • 官方要求:只使用常数级额外空间(这是本题相对第 1 题的关键差异:不许用哈希表)

示例:

  • 示例 1:numbers = [2,7,11,15], target = 9 → 输出 [1,2](2 + 7 = 9)
  • 示例 2:numbers = [2,3,4], target = 6 → 输出 [1,3](2 + 4 = 6)
  • 示例 3:numbers = [-1,0], target = -1 → 输出 [1,2]

先思考#

  1. 无序版(第 1 题)用哈希表记”看过的数”,为什么这里被禁止?有序性提供了什么哈希表没有的信息?
  2. 左端 + 右端的和如果大于 target,说明右端这个数与任何更左的数相加会怎样?由此哪一端可以安全地”永久”排除?
  3. 两个指针各自单调移动、永不回退,循环最多执行多少次?
  4. 输出是 1-based,数组是 0-based,最后一步别忘 +1。

参考答案#

解题思路(推荐解法:对撞双指针):初始化 left = 0,right = n - 1。每轮计算 s = numbers[left] + numbers[right]:若 s == target 返回 [left+1, right+1];若 s > target,则 numbers[right] 与当前最小者相加都已超靶,右端不可能再出现在任何解中,right -= 1;若 s < target,则 numbers[left] 与最大者相加都不够,左端被永久排除,left += 1。每次移动都排除一个元素,指针相遇前必然命中(题目保证恰有一解)。

正确性说明:设唯一解为 (i, j)。左指针到达 i 之前,每次 left += 1 排除的元素都与解无关(因为它与当时的 right(≥ j)相加小于 target,与任何更小元素相加只会更小);同理右指针到达 j 之前排除的元素与解无关。由于解唯一且两指针永不跳过 i 或 j,最终必然同时停在 (i, j)。每个元素被排除一次,循环至多 n−1 轮。

时间复杂度:O(n)——两指针合计至多 n−1 次移动。 空间复杂度:O(1)——仅两个下标变量,满足官方常数空间要求;相比哈希表方案(O(n) 空间),用”有序性”换掉了辅助结构,这正是本题的考察意图。

Python 参考代码:

from typing import List
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right:
s = numbers[left] + numbers[right]
if s == target:
return [left + 1, right + 1] # 1-based
if s > target:
right -= 1 # 右端数与任何更左元素相加都会超靶
else:
left += 1 # 左端数与任何更右元素相加都不够
return [] # 题目保证有解,不会到达

关键边界情况:

  • 重复元素(如 [3,3], target=6):两指针分别指向两个不同下标的 3,left < right 条件保证不重复使用同一元素。
  • 负数与 0(示例 3):算法只依赖和的比较,不依赖元素正负。
  • 最小规模 n=2:一轮循环直接命中。
  • 输出 1-based:+1 转换是最常见的提交错误。

测试用例:

  1. numbers=[2,7,11,15], target=9 → [1,2](官方示例 1)
  2. numbers=[3,3], target=6 → [1,2](重复元素)
  3. numbers=[-1,0], target=-1 → [1,2](负数与零)
  4. numbers=[1,2,3,4,4,9,56,90], target=8 → [4,5](中间位置的重复值命中)

面试官可能的变体或追问:如果数组有序但要求返回所有不重复的解对(LeetCode 15 三数之和的前置形态),怎么处理重复跳过?——命中后两侧同时越过相等区段(while left < right and numbers[left] == numbers[left+1]: left += 1 等),继续搜索而不是返回;进一步追问”三数之和”就是在固定一个数后对本题模式的外层套用。若数组无序但允许 O(n) 空间,则退回哈希表——两种方案的取舍正是”结构换资源”的直接例证。

可迁移的解题能力:对撞双指针的模式识别——“有序 + 找配对/凑和/判回文”这类信号出现时,先想 O(n)/O(1) 的对撞指针而不是哈希表;每步移动排除一个元素的”淘汰论证”式正确性表达,也是面试中展示推理严密性的标准范式。

与 AI Agent 工程的联系(仅自然联系,不强加):上一题 S02 的取舍与本题主线一致——问题自带结构(有序性 / 数据分级 / 文档结构)时应优先利用结构换取确定性与资源节省,而不是套用通用解(哈希表 / 通用存储 / 通用解析)然后为其代价买单。

追问#

追问:如果数组有序但要求返回所有不重复的解对,怎么改?

回答要点:命中后不返回,而是两侧同时越过相等区段(while left < right and numbers[left] == numbers[left+1]: left += 1、对称处理右端)后继续收缩搜索;这是 LeetCode 15 三数之和内层循环的前置形态。若数组无序且允许 O(n) 空间,退回哈希表——两种方案的取舍正是”结构换资源”的直接例证。

关键点#

  1. 对撞指针每步移动排除一个元素:s > target 永久排除右端、s < target 永久排除左端,这是 O(n) 与正确性的共同来源。
  2. 有序性是用 O(1) 空间替代哈希表 O(n) 空间的前提;题目禁用额外空间正是为了逼出这个模式。
  3. left < right 保证不重复使用同一元素,且天然处理重复值命中([3,3], target=6)。
  4. 输出 1-based:返回前必须 +1,是最常见的提交错误。

常见误区#

  1. 无脑套哈希表: ignoring 有序性直接用 O(n) 空间,解法正确但违背题目约束,面试中会错过考察点;正确做法是先声明”数组有序,用对撞指针可省掉哈希表”。
  2. 移动方向写反或同时移动两端:s > target 时应只动右端;若同时移动 left += 1 会跳过唯一解。
  3. 返回 0-based 下标:本题下标从 1 开始,与第 1 题不同,返回前忘加 1 会全部判错。

延伸学习#

  1. LeetCode 15 三数之和(对撞指针在去重与多指针场景的推广,官方题目页):https://leetcode.cn/problems/3sum/ (方向性推荐,本轮未核验)
  2. LeetCode 官方题解正文(本题证明与变体讨论):https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/solution/ (仅核验题目页存在,题解正文未核验)

延伸资料(当日重要主题)#

  1. PostgreSQL 官方文档 PREPARE TRANSACTION(两阶段提交、in-doubt 事务与运维代价):https://www.postgresql.org/docs/current/sql-prepare-transaction.html (已核验)
  2. Redis 官方文档 Replication(异步复制、WAIT 边界、min-replicas-to-write):https://redis.io/docs/latest/operate/oss_and_stack/management/replication/ (已核验)
  3. Anthropic Prompt Engineering Overview(提示词工程技巧与适用边界):https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/overview (已核验)
  4. LeetCode 167 官方题目页:https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/ (已核验)
  5. Martin Kleppmann《How to do distributed locking》(fencing token 正确性论证):https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html (方向性推荐,未核验)

当日重点#

  1. 2PC 的承诺与代价是同一枚硬币:prepare 换来原子性,也制造了”协调者不恢复就全局持锁”的阻塞窗口;选择 2PC 还是 Saga,本质是选择把一致性代价放在协议层还是业务层。
  2. 提示词是分布选择器不是能力注入器:判断”该不该改提示词”用采样判据——正确答案偶尔出现才值得调提示词;硬约束永远外置。
  3. 表格在 RAG 里的失效是结构性损伤:切片切断结构、行块缺列语义、整表供给缺失三层叠加;修复靠结构感知解析 + 双层索引 + 整表注入,不靠调参。
  4. 异步复制的丢失窗口是存储的设计属性:确认成功 ≠ 持久到副本;锁的正确性必须落在资源侧 fencing token,而不是寄望锁键不丢。
  5. 有序数组是对撞指针的入场券:167 用 O(1) 空间完成两数之和,淘汰论证是正确性表达的标准范式。

运行信息#

  • 本次参考的历史题目或文件:题目索引.md(130 条,窗口 2026-08-12 至 2026-11-12);2026-11/2026-11-11-AI-Agent-面试训练.md(主线衔接与 LeetCode 轮换建议);2026-10-21 分布式锁题、2026-11-10 Embedding 迁移题的结论在本轮 S02 / S01 中作为既有结论引用。
  • 配置读取状态:成功,实际配置版本 v8;无暂停类型,观察项未启用。
  • 去重结果:窗口内无同题、换皮题或同 LeetCode 题号(167 未使用过;最近已用 347、151);本轮选题同时响应上轮标注的覆盖优先级(RAG 与搜索、LLM 应用工程)与 LeetCode 轮换建议(双指针对撞)。
  • 链接核验:LC 167、PostgreSQL PREPARE TRANSACTION、Redis Replication、Anthropic Prompt Engineering Overview 已于 2026-11-12 实际访问核验;Unstructured 文档、OpenAI Prompt Engineering Guide、Kleppmann 文章、Kafka Transactions 文档为方向性推荐,已在对应位置标注未核验。
  • 本文件由每日出题任务自动生成;未执行精选;未声称任何远端同步状态。
2026-11-12 AI Agent 面试训练
https://vissssa.github.io/posts/ai-agent-interview-2026-11-12/
作者
Siegward
发布于
2026-11-12
许可协议
CC BY-NC-SA 4.0