JJava 知识库
JAVA INTERVIEW

高频面试题

ES高级约 3 分钟

数据库和 ES 同时保存商品数据,更新并发、消息乱序和补偿怎么处理?

参考回答约 3 分钟 · 口语表达
先说结论

先说结论:Elasticsearch 为操作分配序列号,并用 primary term 区分主分片任期。更新时携带 ifseqno 与 ifprimaryterm,只有文档仍是读取时版本才执行,否则返回冲突,由业务决定重读、合并或放弃。

01

我先给结论,再说明它在项目里解决什么问题。理解乐观并发控制、版本冲突、主副本复制及数据库同步方案。Elasticsearch 为操作分配序列号,并用 primary term 区分主分片任期。更新时携带 ifseqno 与 ifprimaryterm,只有文档仍是读取时版本才执行,否则返回冲突,由业务决定重读、合并或放弃。 这能防止同一文档的丢失更新,但不能自动让关系数据库和 Elasticsearch 形成分布式事务。

02

核心机制我会按一次真实执行过程来讲。沿着「写请求带版本条件 → 主分片顺序执行 → 复制到 in-sync 副本 → refresh 提供搜索可见性 → 失败重试与外部数据源对账」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 写请求带版本条件 seqno 与 primaryterm 标识主分片上的操作顺序,可用于乐观并发控制避免旧写覆盖新写。 主分片顺序执行 主分片对同一文档串行裁决,ifseqno/ifprimaryterm 不匹配时返回冲突。

03

实现细节只抓关键入口,不会整段背源码。InternalEngine#planIndexingAsPrimary:seqno/primaryterm 裁决。 seqno/primaryterm:乐观锁令牌。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。

04

放到生产使用时,我会关注参数和验证数据。乱序重放 v1/v3/v2 和删除墓碑,验证旧版本被拒绝。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「version conflict」为主基线,记录值应满足「记录稳态基线」;同时保存 版本冲突数、CDC Lag,使后续变化能够回到同一时间轴比较。

05

最后补充常见误区和使用边界。重放队列把较早更新晚于新更新送达,普通 index API 覆盖了当前状态。把数据库单调版本作为 externalgte 或脚本条件后,旧事件被拒绝并记录。 用 last-write-wins 接受乱序覆盖:版本冲突数:外部事件带单调版本。 把 refresh 延迟误判为数据丢失:CDC Lag:删除保留墓碑版本。 方案:更适合的场景:主要收益:代价与边界。 内部 seqno 控制:读改写都在同一 ES 索引:原生并发控制:不能跨重建或外部系统直接沿用。 外部业务版本:CDC/多源按单调版本同步:能拒绝乱序旧事件:版本必须全局可比较且处理删除。 全量重建 + alias:大范围修复或 Mapping 变化:结果可审计、切换原子:资源、时间和增量追平复杂。