applyEntryNormal 方法的主要作用是将类型为 EntryNormal 的 Raft 日志条目应用到 EtcdServer 中。这个方法处理了多个复杂的业务逻辑,包括一致性索引更新、不同版本请求的处理、资源空间检查等。
目录
一、方法完整代码
// applyEntryNormal apples an EntryNormal type raftpb request to the EtcdServer
func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) {
shouldApplyV3 := membership.ApplyV2storeOnly
var ar *applyResult
index := s.consistIndex.ConsistentIndex()
if e.Index > index {
// set the consistent index of current executing entry
s.consistIndex.SetConsistentApplyingIndex(e.Index, e.Term)
shouldApplyV3 = membership.ApplyBoth
defer func() {
// The txPostLockInsideApplyHook will not get called in some cases,
// in which we should move the consistent index forward directly.
newIndex := s.consistIndex.ConsistentIndex()
if newIndex < e.Index {
s.consistIndex.SetConsistentIndex(e.Index, e.Term)
}
}()
}
s.lg.Debug("apply entry normal",
zap.Uint64("consistent-index", index),
zap.Uint64("entry-index", e.Index),
zap.Bool("should-applyV3", bool(shouldApplyV3)))
// raft state machine may generate noop entry when leader confirmation.
// skip it in advance to avoid some potential bug in the future
if len(e.Data) == 0 {
s.notifyAboutFirstCommitInTerm()
// promote lessor when the local member is leader and finished
// applying all entries from the last term.
if s.isLeader() {
s.lessor.Promote(s.Cfg.ElectionTimeout())
}
return
}
var raftReq pb.InternalRaftRequest
if !pbutil.MaybeUnmarshal(&raftReq, e.Data) { // backward compatible
var r pb.Request
rp := &r
pbutil.MustUnmarshal(rp, e.Data)
s.lg.Debug("applyEntryNormal", zap.Stringer("V2request", rp))
s.w.Trigger(r.ID, s.applyV2Request((*RequestV2)(rp), shouldApplyV3))
return
}
s.lg.Debug("applyEntryNormal", zap.Stringer("raftReq", &raftReq))
if raftReq.V2 != nil {
req := (*RequestV2)(raftReq.V2)
s.w.Trigger(req.ID, s.applyV2Request(req, shouldApplyV3))
return
}
id := raftReq.ID
if id == 0 {
id = raftReq.Header.ID
}
needResult := s.w.IsRegistered(id)
if needResult || !noSideEffect(&raftReq) {
if !needResult && raftReq.Txn != nil {
removeNeedlessRangeReqs(raftReq.Txn)
}
ar = s.applyV3.Apply(&raftReq, shouldApplyV3)
}
// do not re-apply applied entries.
if !shouldApplyV3 {
return
}
if ar == nil {
return
}
if ar.err != ErrNoSpace || len(s.alarmStore.Get(pb.AlarmType_NOSPACE)) > 0 {
s.w.Trigger(id, ar)
return
}
lg := s.Logger()
lg.Warn(
"message exceeded backend quota; raising alarm",
zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
zap.String("quota-size", humanize.Bytes(uint64(s.Cfg.QuotaBackendBytes))),
zap.Error(ar.err),
)
s.GoAttach(func() {
a := &pb.AlarmRequest{
MemberID: uint64(s.ID()),
Action: pb.AlarmRequest_ACTIVATE,
Alarm: pb.AlarmType_NOSPACE,
}
s.raftRequest(s.ctx, pb.InternalRaftRequest{Alarm: a})
s.w.Trigger(id, ar)
})
}
二、方法详细解析
1. 检查是否需要应用 V3 存储
shouldApplyV3 := membership.ApplyV2storeOnly
- 功能:设置一个标志
shouldApplyV3,用于决定是否需要同时应用 V3 存储。 - 作用:默认应用 V2 存储,只有在条件满足时才应用 V3 存储。
2. 一致性索引检查
index := s.consistIndex.ConsistentIndex()
if e.Index > index {
// set the consistent index of current executing entry
s.consistIndex.SetConsistentApplyingIndex(e.Index, e.Term)
shouldApplyV3 = membership.ApplyBoth
defer func() {
newIndex := s.consistIndex.ConsistentIndex()
if newIndex < e.Index {
s.consistIndex.SetConsistentIndex(e.Index, e.Term)
}
}()
}
- 功能:检查 Raft 条目的索引
e.Index是否大于当前的一致性索引。 - 作用:
- 如果当前条目的索引大于一致性索引,则更新一致性索引,设置当前条目为正在执行的条目。
- 如果需要,切换到应用 V3 存储。
- 使用
defer确保在方法执行完成时,更新一致性索引。
3. 日志记录
s.lg.Debug("apply entry normal",
zap.Uint64("consistent-index", index),
zap.Uint64("entry-index", e.Index),
zap.Bool("should-applyV3", bool(shouldApplyV3)))
- 功能:记录调试日志,输出当前一致性索引、条目索引以及是否应用 V3 存储。
- 作用:帮助开发者调试和跟踪应用进度。
4. 跳过空条目
if len(e.Data) == 0 {
s.notifyAboutFirstCommitInTerm()
if s.isLeader() {
s.lessor.Promote(s.Cfg.ElectionTimeout())
}
return
}
- 功能:检查条目数据是否为空。
- 作用:
- 如果是空条目(例如 Raft leader 的确认条目),则跳过该条目的处理。
- 通知这是该任期中的第一个提交,并在需要时提升
lessor(如果当前是 leader)。
5. 反序列化 Raft 请求
var raftReq pb.InternalRaftRequest
if !pbutil.MaybeUnmarshal(&raftReq, e.Data) { // backward compatible
var r pb.Request
rp := &r
pbutil.MustUnmarshal(rp, e.Data)
s.lg.Debug("applyEntryNormal", zap.Stringer("V2request", rp))
s.w.Trigger(r.ID, s.applyV2Request((*RequestV2)(rp), shouldApplyV3))
return
}
- 功能:尝试反序列化 Raft 请求。如果失败,则尝试反序列化为 V2 请求。
- 作用:
- 兼容旧版本的请求处理。
- 如果是 V2 请求,触发
applyV2Request来处理它。
6. 处理 Raft 请求
s.lg.Debug("applyEntryNormal", zap.Stringer("raftReq", &raftReq))
if raftReq.V2 != nil {
req := (*RequestV2)(raftReq.V2)
s.w.Trigger(req.ID, s.applyV2Request(req, shouldApplyV3))
return
}
- 功能:如果是 Raft 请求(
raftReq),则进一步处理。 - 作用:
- 如果是 V2 请求,则触发处理 V2 请求的逻辑。
7. 处理 V3 请求
id := raftReq.ID
if id == 0 {
id = raftReq.Header.ID
}
needResult := s.w.IsRegistered(id)
if needResult || !noSideEffect(&raftReq) {
if !needResult && raftReq.Txn != nil {
removeNeedlessRangeReqs(raftReq.Txn)
}
ar = s.applyV3.Apply(&raftReq, shouldApplyV3)
}
- 功能:根据 Raft 请求的 ID 判断是否需要处理结果,并应用 V3 存储。
- 作用:
- 检查请求是否需要返回结果,或者请求是否有副作用。
- 如果是 V3 请求,则调用
applyV3.Apply进行处理。
8. 避免重新应用已应用的条目
if !shouldApplyV3 {
return
}
- 功能:如果不需要应用 V3 存储,直接返回,避免重复处理。
9. 处理应用结果
if ar == nil {
return
}
if ar.err != ErrNoSpace || len(s.alarmStore.Get(pb.AlarmType_NOSPACE)) > 0 {
s.w.Trigger(id, ar)
return
}
- 功能:检查应用结果
ar是否为空,以及是否有错误。 - 作用:
- 如果结果存在且没有空间错误,则触发请求的回调。
10. 处理存储空间不足的警告
lg := s.Logger()
lg.Warn(
"message exceeded backend quota; raising alarm",
zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
zap.String("quota-size", humanize.Bytes(uint64(s.Cfg.QuotaBackendBytes))),
zap.Error(ar.err),
)
s.GoAttach(func() {
a := &pb.AlarmRequest{
MemberID: uint64(s.ID()),
Action: pb.AlarmRequest_ACTIVATE,
Alarm: pb.AlarmType_NOSPACE,
}
s.raftRequest(s.ctx, pb.InternalRaftRequest{Alarm: a})
s.w.Trigger(id, ar)
})
- 功能:如果存储空间不足,则发出警告,并触发警报。
- 作用:
- 记录警告日志,说明存储空间不足。
- 异步发送警报请求,激活存储空间不足的警报。
三、方法总结:
该函数处理了不同类型的 Raft 日志条目,确保一致性索引被更新,并根据请求类型(V2 或 V3)进行不同的处理。它还检查了存储空间,并在空间不足时触发警报。
后续将继续对方法s.applyV3.Apply(&raftReq, shouldApplyV3)进行深入分析

被折叠的 条评论
为什么被折叠?



