✅ 副本分片 globalCheckpoint 的更新推进方式(仅两种)
1. 副本恢复完成时(Recovery Finalization)
-
触发时机:副本从主分片完成恢复(包括文件和 translog 恢复)后,进入 finalize 阶段。
-
数据来源 :主分片在
FinalizeRecoveryRequest中显式携带当前最新的globalCheckpoint。 -
副本操作 :
indexShard.updateGlobalCheckpointOnReplica(globalCheckpoint, "finalizing recovery"); -
作用:让刚恢复的副本立即获知当前全局已安全提交的操作边界,避免误删尚未持久化的 translog。
📌 这是副本首次获知 global checkpoint 的主要途径。
2. 正常写入流程中(Replica Operation)
-
触发时机 :主分片处理写入请求(index/delete/update 等)并向副本发送
ReplicaRequest(如BulkShardRequest)时。 -
数据来源 :主分片将当前
globalCheckpoint作为元数据字段嵌入到每个 replica 请求中。 -
副本操作 :在获取操作许可时(
acquireReplicaOperationPermit)调用:updateGlobalCheckpointOnReplica(globalCheckpoint, "operation"); -
特点 :
- 高频、低延迟同步:只要有写入,就自动更新。
- 原子性保障:确保在执行操作前,副本已知晓最新的安全边界。
📌 这是副本在运行期间持续跟进 global checkpoint 的主要方式。
❌ 不属于"推进"方式的机制
GlobalCheckpointSyncAction
- 目的 :确保副本(和主)将内存中已有的
globalCheckpoint持久化到磁盘 (写入translog.ckp)。 - 不传递新值 :请求体为空,不包含
globalCheckpoint数值。 - 不改变内存值 :仅触发
sync(),用于崩溃恢复时的数据一致性。 - 典型场景:分片即将变为 inactive(如索引关闭)且近期无写入。
🔸 它是 持久化兜底机制 ,不是 值传播或推进机制。
🧠 关键区分
| 操作 | 是否更新内存中的 globalCheckpoint? |
是否涉及网络传值? | 目的 |
|---|---|---|---|
| Recovery Finalize | ✅ 是 | ✅ 是 | 初始化副本的 checkpoint |
| Replica Write Request | ✅ 是 | ✅ 是 | 持续同步最新 checkpoint |
| GlobalCheckpointSyncAction | ❌ 否 | ❌ 否 | 确保持久化(落盘) |
✅ 总结一句话
副本分片的
globalCheckpoint内存值仅通过两种方式推进:① 恢复完成时由主分片显式告知;② 正常写入时由主分片随请求附带传递。其他机制(如GlobalCheckpointSyncAction)仅用于确保持久化,不参与值的更新。
这是 Elasticsearch 实现 高吞吐、强一致性、崩溃安全 的关键设计之一。
下面我们将 极其详细地 梳理 Elasticsearch 中 副本分片(Replica Shard)的 globalCheckpoint 是如何被更新和推进的,包括:
- 背景概念澄清
- 两种核心推进机制的完整流程(含代码路径与交互细节)
- 为什么只有这两种方式
- 其他相关机制(如
GlobalCheckpointSyncAction、RetentionLease)的作用边界 - 实际场景举例
- 源码关键点佐证
🧩 一、背景:什么是 globalCheckpoint?
在 Elasticsearch 的 序列号(Sequence Number)与 Checkpoint 机制中:
localCheckpoint:每个分片(主或副本)本地已安全持久化的最大 seqno。globalCheckpoint:所有活跃副本都已确认持久化的最大 seqno。它代表"可以安全删除 translog"的边界。- 由主分片维护并传播 :主分片负责计算
globalCheckpoint = min(active_replicas.localCheckpoint),然后通知所有副本。
✅ 副本本身 不计算 global checkpoint,只 接收并记录 主分片告诉它的值。
🔁 二、副本 globalCheckpoint 更新的两种核心方式
方式 1️⃣:副本恢复完成时(Recovery Finalization)
📌 触发条件
- 副本 shard 处于 UNASSIGNED → INITIALIZING → STARTED 过程
- 完成 Phase 1(文件复制) + Phase 2(translog 重放)
- 进入 finalize recovery 阶段
🔄 流程详解
-
主分片构造
FinalizeRecoveryRequest-
在
RecoverySourceHandler.finalize()中:final long globalCheckpoint = indexShard().getGlobalCheckpoint(); transportService.sendRequest( targetNode, RecoveryTarget.Actions.FINALIZE, new FinalizeRecoveryRequest(recoveryId, shardId, globalCheckpoint), ... );
-
-
副本接收请求并更新
-
在
RecoveryTarget.doFinalize()中:indexShard.updateGlobalCheckpointOnReplica(request.globalCheckpoint(), "finalizing recovery"); -
此方法内部:
public void updateGlobalCheckpointOnReplica(long globalCheckpoint, String reason) { assert Thread.holdsLock(this); if (globalCheckpoint > this.globalCheckpoint) { this.globalCheckpoint = globalCheckpoint; // 注意:此时不一定立即 sync 到磁盘! } }
-
-
结果
- 副本内存中的
globalCheckpoint被设为恢复完成时刻主分片的值 - 后续可基于此值安全清理 translog
- 副本内存中的
💡 此时副本可能还未持久化该值(取决于
Translog.Durability),但内存值已更新。
方式 2️⃣:正常写入流程中(Replica Operation)
📌 触发条件
- 主分片处理写入请求(如 IndexRequest)
- 执行到
TransportReplicationAction的shardOperationOnPrimary - 向副本发送
ReplicaRequest
🔄 流程详解
-
主分片在构建 replica 请求时嵌入
globalCheckpoint-
以
BulkShardRequest为例:BulkShardRequest bulkShardRequest = new BulkShardRequest( shardId, timeout, items, primaryTerm, primary.getGlobalCheckpoint() // ← 关键! );
-
-
副本在执行操作前更新 checkpoint
-
在
TransportReplicationAction.shardOperationOnReplica路径中:indexShard.acquireReplicaOperationPermit(..., listener); -
在
acquireReplicaOperationPermit内部(IndexShard.java):updateGlobalCheckpointOnReplica(replicaRequest.getGlobalCheckpoint(), "operation"); -
同样调用
updateGlobalCheckpointOnReplica(),逻辑同上
-
-
特点
- 每次写入都携带最新值,即使 global checkpoint 未变
- 高频同步:保证副本几乎实时跟进
- 轻量级:只是内存赋值,无额外 RPC
⚠️ 注意:即使没有新操作推进 local checkpoint,只要主分片的 global checkpoint 更新了(比如其他副本追上了),下次写入就会把新值带给所有副本。
❌ 三、哪些操作 不会 推进副本的 globalCheckpoint?
1. GlobalCheckpointSyncAction
- 作用 :触发
indexShard.sync()→ 将当前globalCheckpoint写入translog.ckp - 不包含任何 checkpoint 值
- 请求体为空 (仅
ShardId) - 目的:durability,非 propagation
2. Background retention lease sync / stats fetch
- 这些操作可能携带
localCheckpoint用于 lease 维护,但 不更新globalCheckpoint
3. Peer recovery 的 phase 1/2
- 只传输数据文件和 translog 操作
- global checkpoint 在 finalize 阶段才传递
🔍 四、为什么只有这两种方式?设计哲学
Elasticsearch 的设计原则是:
"避免额外的协调开销,复用已有数据通道"
- 写入路径已经存在 → 顺带传 global checkpoint(零成本)
- 恢复是特殊状态 → 单独一次显式同步(必要开销)
- 不引入独立的 "checkpoint heartbeat" 或 "gossip" 机制 → 减少网络流量和复杂性
同时:
- 副本不需要主动"拉取" global checkpoint(被动接收即可)
- 主分片全权负责其计算与分发
🧪 五、实际场景举例
场景 A:索引刚创建,写入 10 条数据
- 主分片 localCP = 9,globalCP = 9(无副本)
- 添加副本 → 触发 recovery
- recovery finalize 时,主告知副本 globalCP = 9
- 副本内存 globalCP = 9
- 后续写入第 11 条 → 主在 replica request 中携带 globalCP(仍为 9,因副本未 ack)
- 副本执行后更新 localCP = 10 → 主检测到 → globalCP = 10
- 下次写入 → 主在请求中携带 globalCP = 10 → 副本更新内存值
场景 B:长时间无写入,但副本追上了
- 主 localCP = 100,副本 localCP = 100
- 主将 globalCP 推进到 100
- 但没有新写入 → 副本内存 globalCP 仍为 99
- 此时若关闭索引 → 触发
GlobalCheckpointSyncAction- 副本发现
lastKnownGlobalCheckpoint = 100(怎么来的?见下)
- 副本发现
❓ 等等!副本怎么知道 globalCP=100 的?
→ 实际上,主分片会在副本的 retention lease 心跳响应中捎带 global checkpoint!
🔄 补充:第三种隐式方式?Retention Lease 心跳
⚠️ 这是一个容易被忽略的细节!
从 Elasticsearch 7.x+ 开始,为了支持 follower index(CCR) 和更精确的 retention lease,主分片会在 处理副本的 retention lease 请求时返回 global checkpoint。
-
副本定期发送
RetentionLeaseSyncAction.Request -
主分片响应中包含:
response = new RetentionLeaseSyncAction.Response(globalCheckpoint); -
副本收到后:
indexShard.updateGlobalCheckpointOnReplica(response.getGlobalCheckpoint(), "retention lease sync");
✅ 所以严格来说,有第三种方式:通过 retention lease 心跳同步!
但注意:
- 该机制主要用于 CCR(跨集群复制) 或 长期无写入的副本保活
- 在普通索引中,如果副本活跃,写入路径已足够;如果不活跃,lease sync 会兜底
- 它仍然是 主 → 副本的 push(通过响应),不是副本主动查询
📌 因此,更完整的说法是:
副本 globalCheckpoint 的更新来源有三种:
- Recovery finalize
- Replica write request
- Retention lease sync response(隐式,常被忽略)
但在 纯写入场景(无 CCR) 中,通常只体现前两种。
📚 六、关键源码位置(Elasticsearch 8.x)
| 功能 | 类/方法 |
|---|---|
| 更新副本 global CP | IndexShard.updateGlobalCheckpointOnReplica() |
| Recovery finalize 发送 | RecoverySourceHandler.finalize() |
| Replica request 携带 | BulkShardRequest.getGlobalCheckpoint() |
| Lease sync 返回 | TransportRetentionLeaseSyncAction.masterOperation() |
| GlobalCheckpointSyncAction | GlobalCheckpointSyncAction.maybeSyncTranslog() |
✅ 最终总结(超详细版)
副本分片的 globalCheckpoint 内存值更新,通过以下 三种机制(按优先级和频率):
| 机制 | 触发条件 | 是否主流 | 是否传值 | 备注 |
|---|---|---|---|---|
| 1. Replica Write Request | 有写入操作 | ✅ 是 | ✅ 是 | 最常见,低延迟 |
| 2. Recovery Finalize | 副本恢复完成 | ✅ 是 | ✅ 是 | 初始化必经之路 |
| 3. Retention Lease Sync | 副本定期心跳(尤其 CCR) | ⚠️ 次要 | ✅ 是 | 防止长期无写入时滞后 |
而:
GlobalCheckpointSyncAction仅做持久化(sync to disk),不更新内存值- 副本 永远不会自己计算 global checkpoint
- 所有值都来自 主分片的主动推送
希望这份超详细解析能彻底解答你的疑问!如需进一步深入某一部分(如 retention lease 机制、translog ckp 格式等),欢迎继续提问。