先理解
刚接触这个知识点
补齐先备概念,读原理与反例,再用自己的话解释为什么。
从核心原理开始 →理解 → 实现 → 排错 → 取舍
考察状态建模、Reducer、并发合并与消息去重。
知识内容核对 2026-10-03 · 原题来源核对 2026-10-02
建议先理解:
确定流程与局部探索的组合 →按当前基础选择起点,也可以依次深入。遇到不熟悉的概念,先回到核心原理;完成后用知识练习检查理解。
LEARN · PRACTICE · REFLECT
先沿着原理、问答和迁移案例阅读。需要检查理解时,再切换巩固练习或展开个人记录。
核心知识 · 并行状态合并的代数与业务语义
先备概念:不可变数据、唯一标识、并发更新
reducer 定义增量怎样成为共享事实。重放要求重复输入不改变结果,并行要求不依赖偶然到达顺序;但去重、排序和冲突处理必须服从业务语义,不能把所有列表都机械变成集合。
两个节点从相同状态出发,各自返回新证据。如果都回传完整列表,后返回者可能覆盖前一个;若都追加,重试又可能产生重复。reducer 的作用是明确字段的更新语义,LangGraph 允许为状态字段定义这样的合并函数。
结合性让批次分组不影响结果,交换性让并发到达顺序不影响结果,幂等性让重复增量不再增加内容。它们是可检查的性质,不是框架自动赋予。按证据 ID 合并可支持去重,但若同 ID 不同正文,需要版本或冲突规则;“最后返回胜出”把网络速度变成事实裁判。
证据集合可按 ID 去重,执行日志可能需要保留每次尝试,金额增量需要唯一事件 ID 防重复,对话消息还需要定义顺序。通用追加 reducer 不适合所有字段。合并通常要设计成确定的纯函数,外部副作用留在节点或工具层,否则一次重放可能再次发信。
先将不可变输入、节点输出、控制状态分开,明确每个字段由谁写。并行分支最好按稳定子任务 ID 写独立结果,汇总节点再合并。Reducer 必须有明确语义;列表追加不等于幂等,普通覆盖可能丢结果。对重复回调和重放用事件 ID 去重,恢复与并发策略要与当前框架版本的行为一起验证。
白板上画出 run_id、输入版本、任务列表、分支结果、错误和终态。输入由入口写一次,分支只能写自己的结果,终态由汇总节点控制。不要让多个分支都改同一个“最终答案”字段。对于图框架,先确认该字段使用默认更新还是显式 Reducer,不能假定框架会自动理解业务合并规则。
假设两个节点返回证据列表,直接追加可以保留两份结果,但重复恢复可能出现相同证据;用稳定 evidence_id 去重更可靠。假设节点返回账户余额,则不能简单求和,因为可能是同一账户的不同快照,应按账户和数据版本保存,再由业务规则选择。对无序并行完成,合并函数最好不依赖到达顺序;需要顺序时必须显式排序或串行化。
结果键使用 run_id、子任务 ID 和输入版本。相同任务的重试写同一逻辑记录,新任务不能复用旧缓存键。一个分支失败时,记录失败状态,不用空列表伪装成功。若需要全部完成,汇总节点验证任务清单与结果清单一致;允许部分结果时,输出缺失项。跨任务共享偏好放入独立存储,不混入临时状态。
构造 A 先完成、B 先完成、A 重复完成、A 失败后重试四种事件顺序。断言不同顺序的最终有效证据一致,重复事件不改变计数,旧输入版本不能覆盖新输入。面试重点是候选人是否能具体说明数据合并契约;只回答“加一个 Reducer”不足以证明能处理真实冲突。
纯函数示例,演示幂等集合合并;业务允许多版本时应单独定义版本选择规则。
def merge_evidence(*batches):
merged = {}
for batch in batches:
for item in batch:
key = item["id"]
if key in merged and merged[key] != item:
raise ValueError("conflicting evidence: " + key)
merged[key] = dict(item)
return [merged[key] for key in sorted(merged)]
a = [{"id": "e1", "version": 1, "text": "approved"}]
b = [{"id": "e2", "version": 1, "text": "checked"}]
assert merge_evidence(a, b, a) == merge_evidence(b, a)
print([x["id"] for x in merge_evidence(a, b, a)])
try:
merge_evidence(a, [{"id": "e1", "version": 2, "text": "changed"}])
except ValueError:
print("conflict rejected")
预期输出
['e1', 'e2']
conflict rejected沿着问题的前提和约束继续向下读。先理解参考解答,再尝试收起答案,用自己的话解释因果和取舍。
第 1 层列表追加为什么不一定幂等?
恢复会重复应用更新,需要从记录身份理解幂等。
列表拼接满足重复输入重复追加:已有 [A] 再应用同一增量 [B] 两次会得到 [A,B,B]。若字段代表唯一证据,应按稳定 ID 去重;若字段代表每次尝试,则重复是否保留取决于 attempt_id。先定义“一条记录代表什么”,再选择集合合并或事件去重,不能只看列表类型。
沿着这个回答继续深入
第 2 层把列表改成字典按 ID 覆盖,是否就解决全部问题?
去重消除了重复项,仍可能悄悄丢掉冲突证据。
只解决同 ID 的数量重复,没有解决内容冲突和顺序。两份正文不同却同 ID,需要不可变内容摘要或版本检查;若覆盖由到达顺序决定,结果仍不稳定。展示排序可另按稳定键生成,但不能改变事实版本的选择。
沿着这个回答继续深入
第 3 层同一文档确实更新了,怎样兼顾去重和保留历史?
冲突未必是错误,也可能是合法版本演进,需要建模历史。
把文档逻辑 ID 与版本 ID 分开,证据以二者及内容摘要标识,历史不可变;当前视图按业务认可的版本关系选取,而非按机器时钟最大值盲选。遇到不可比较的并发版本保留冲突,依赖旧版本的结论标过期。
第 1 层两个节点返回同一 ID、不同正文怎么办?
合并不仅是去重,还决定哪份内容能成为可信状态。
比较版本和内容摘要。同版本不同正文违反不可变契约,应拒绝合并并追查来源;不同版本则都保留或按明确版本规则选择,记录哪些下游结果失效。不要静默使用后返回的正文,日志应足以定位产生冲突的节点与输入版本。
第 1 层恢复后输入版本变化,旧结果还能使用吗?
幂等更新不能证明输出仍适用,需要再检查输入一致性。
根据依赖判断。结果依赖的输入字段或文档版本改变,就不能直接用于当前决策;无关字段变化可继续复用。保存结果的输入摘要和依赖列表,恢复时对照,必要时重跑节点。旧结果可作为历史证据保留,但“存在”与“当前有效”是不同状态。
先找出改变的条件,再判断原方案中哪些前提仍成立。下面的案例是教学推演,便于将原理迁移到新问题。
改变的条件:集合记录变成可重复投递的金额事件
延伸问题:怎样合并“增加 100”而不重复计入?
为每个真实业务事件分配稳定 event_id,账本先去重事件再求和。两次不同的合法增加即使金额相同也必须都计入,所以不能按数值去重。若需要冲正,用关联原事件的新事件,保留原始事实;生产原子性由数据库约束保证。
保持不变的原理:重复的是同一业务事件,而不是相同内容。
改变的条件:并行证据变成有因果顺序的消息
延伸问题:可以直接用无序集合 reducer 吗?
不能。保存消息身份、父调用关系和可确定的序号,合并后按因果约束构建视图。并行独立回复可按稳定次序展示,但工具结果必须关联正确调用,不能让排序把它归到另一个问题。顺序未确定时显式保留并发关系。
保持不变的原理:合并性质必须服务于业务含义,不能为满足交换性牺牲因果关系。
依据公开技术资料设计;参考资料支持技术机制,场景与评分标准为本站设计,不代表某公司面试原题。 新增问答与迁移案例用于原理讲解,来源核查与案例运行验证分别记录。
读完后可以对照这些标准解释原理、边界和取舍。掌握程度由你自评;需要进一步验证时,再完成下方小任务。
实现按 evidence_id 合并两批证据的伪代码,并说明同 ID 不同版本的处理。