bigywater.tech
Elasticsearch · 104

乐观并发控制与 seq_no

真实带着正确的if_seq_no和if_primary_term写入成功后验证seq_no前进,再用已经过期的seq_no重复写入验证服务端精确拒绝并回显当前真实版本,揭示乐观并发控制靠比较版本标识而非加锁的真实机制。

带正确seq_no/primary_term写入
真实成功,seq_no前进到1
用过期的seq_no=0再写
真实直接409版本冲突报错
错误信息
真实精确回显声明值与服务端当前真实值的差异

做的事:真实索引一篇文档,拿到它的seq_no和primary_term,再用这组值去更新,验证成功后seq_no会不会前进

代码PUT /occ_test/_doc/1 {"n":1} # 真实拿到 _seq_no=0, _primary_term=1 PUT /occ_test/_doc/1?if_seq_no=0&if_primary_term=1 {"n":2}

验证:带着正确的seq_no和primary_term去写,真实成功了,seq_no真实前进到了1

实测 · 本机 Elasticsearch 8.15.0 执行输出
写入前: _seq_no=0, _primary_term=1
带if_seq_no=0&if_primary_term=1写入: result="updated", _seq_no=1
  ← 真实写入成功,seq_no也真实跟着前进了

这组真实结果展示了乐观并发控制最基本的工作方式:客户端真实先读出文档当前的_seq_no和_primary_term,带着这组"我以为的当前版本标识"去发起写入,只要这组值真实跟服务端此刻记录的完全一致,写入就真实被允许执行,并且_seq_no真实会在这次写入成功后递增,标志着文档真实进入了下一个版本。

真实用刚才那个已经过期的seq_no=0再写一次,看服务端会不会真的拒绝它

代码PUT /occ_test/_doc/1?if_seq_no=0&if_primary_term=1 {"n":3} # 真实复用已经过期的seq_no

验证:真实直接409报错,错误信息精确写出了"你以为的seq_no"和"服务端真实的当前seq_no"两者的差异

实测 · 本机执行输出
→ 409 version_conflict_engine_exception:
  "[1]: version conflict, required seqNo [0], primary term [1].
   current document has seqNo [1] and primary term [1]"
这条真实报错把乐观并发控制的运作机制摊开得清清楚楚:它真实不是靠加锁阻止并发,而是真实在每次写入前做一次"比较并交换"——服务端真实检查请求里声明的if_seq_no/if_primary_term是否跟文档此刻真实的_seq_no/_primary_term完全一致,一致才真实允许写入通过并把seq_no向前推进,不一致(本次实测请求里声明的是过期的0,服务端真实已经是1)就直接拒绝,把真实的当前值原样回显在错误信息里,让调用方能真实看清楚"你以为的版本"和"实际的版本"差在哪。_seq_no真实是单调递增的序号,标记着这个分片上发生的每一次写入操作的真实顺序;_primary_term真实是另一重更粗粒度的版本标识,只有在主分片真实发生故障转移、换了一个新的主分片接管写入的时候才会递增——两者合在一起,才能真实在主分片切换这种真实复杂的场景下,依然保证"这个seq_no是在哪一任主分片治下产生的"这个判断不会出错。本次实测在单节点、主分片没有发生过切换的场景下,_primary_term真实一直稳定在1,真正起到并发检测作用的是持续递增的_seq_no。

分步演示:乐观并发控制从正确版本标识写入成功到过期标识被真实拒绝的完整验证

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

乐观并发控制真实靠比较seq_no和primary_term,不一致就直接拒绝写入
这一步新验证的seq_no状态

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 if_seq_no 与 if_primary_term 参数
    京ICP备2026067502号-1京公网安备11010502063487号