【ETCD】【源码阅读】深入解析 EtcdServer.applyEntryNormal方法

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)进行深入分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值