【ETCD】[源码阅读]深度解析 EtcdServer 的 processInternalRaftRequestOnce 方法

在分布式系统中,etcd 的一致性与高效性得益于其强大的 Raft 协议模块。而 processInternalRaftRequestOnce 是 etcd 服务器处理内部 Raft 请求的核心方法之一。本文将从源码角度解析这个方法的逻辑流程,帮助读者更好地理解 etcd 的内部实现。

方法源码

go 复制代码
func (s *EtcdServer) processInternalRaftRequestOnce(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
	ai := s.getAppliedIndex()
	ci := s.getCommittedIndex()
	if ci > ai+maxGapBetweenApplyAndCommitIndex {
		return nil, ErrTooManyRequests
	}

	r.Header = &pb.RequestHeader{
		ID: s.reqIDGen.Next(),
	}

	// check authinfo if it is not InternalAuthenticateRequest
	if r.Authenticate == nil {
		authInfo, err := s.AuthInfoFromCtx(ctx)
		if err != nil {
			return nil, err
		}
		if authInfo != nil {
			r.Header.Username = authInfo.Username
			r.Header.AuthRevision = authInfo.Revision
		}
	}

	data, err := r.Marshal()
	if err != nil {
		return nil, err
	}

	if len(data) > int(s.Cfg.MaxRequestBytes) {
		return nil, ErrRequestTooLarge
	}

	id := r.ID
	if id == 0 {
		id = r.Header.ID
	}
	ch := s.w.Register(id)

	cctx, cancel := context.WithTimeout(ctx, s.Cfg.ReqTimeout())
	defer cancel()

	start := time.Now()
	err = s.r.Propose(cctx, data)
	if err != nil {
		proposalsFailed.Inc()
		s.w.Trigger(id, nil) // GC wait
		return nil, err
	}
	proposalsPending.Inc()
	defer proposalsPending.Dec()

	select {
	case x := <-ch:
		return x.(*applyResult), nil
	case <-cctx.Done():
		proposalsFailed.Inc()
		s.w.Trigger(id, nil) // GC wait
		return nil, s.parseProposeCtxErr(cctx.Err(), start)
	case <-s.done:
		return nil, ErrStopped
	}
}

方法解析

1. 校验状态与索引

go 复制代码
ai := s.getAppliedIndex()
ci := s.getCommittedIndex()
if ci > ai+maxGapBetweenApplyAndCommitIndex {
	return nil, ErrTooManyRequests
}

getAppliedIndexgetCommittedIndex 分别获取当前节点的已应用索引和已提交索引。如果两者的差值过大,说明节点存在过多未应用的日志条目,可能导致性能问题,因此直接返回错误。

  • maxGapBetweenApplyAndCommitIndex:定义了允许的最大索引差距。
  • 防止机制:避免提交速度过快导致内存积压。

2. 生成请求头

go 复制代码
r.Header = &pb.RequestHeader{
	ID: s.reqIDGen.Next(),
}

每个请求分配一个唯一的 ID,以便后续跟踪和处理。

3. 身份验证检查

go 复制代码
if r.Authenticate == nil {
	authInfo, err := s.AuthInfoFromCtx(ctx)
	if err != nil {
		return nil, err
	}
	if authInfo != nil {
		r.Header.Username = authInfo.Username
		r.Header.AuthRevision = authInfo.Revision
	}
}
  • 目的:除认证请求外,其他请求需要验证用户身份。
  • 逻辑
    1. 调用 AuthInfoFromCtx 从上下文中提取用户身份。
    2. 将身份信息写入请求头,供后续处理。

4. 请求大小检查

go 复制代码
if len(data) > int(s.Cfg.MaxRequestBytes) {
	return nil, ErrRequestTooLarge
}
  • 目的:防止超大请求导致内存或网络问题。
  • 机制:检查请求序列化后的大小是否超过配置的最大限制。

5. 注册请求等待通道

go 复制代码
id := r.ID
if id == 0 {
	id = r.Header.ID
}
ch := s.w.Register(id)
  • 注册通道 :使用请求 IDs.w(wait 组件)中注册一个等待通道,用于异步获取结果。

6. 发起 Raft 提案

go 复制代码
cctx, cancel := context.WithTimeout(ctx, s.Cfg.ReqTimeout())
defer cancel()

start := time.Now()
err = s.r.Propose(cctx, data)
  • 发起提案 :调用 s.r.Propose 将请求数据交给 Raft 模块进行分布式一致性处理。
  • 超时控制 :通过 Context.WithTimeout 设置提案的最大执行时间,避免长期阻塞。
  • 错误处理:如果提案失败,增加失败计数,并触发通道清理。

7. 等待提案结果

go 复制代码
select {
case x := <-ch:
	return x.(*applyResult), nil
case <-cctx.Done():
	proposalsFailed.Inc()
	s.w.Trigger(id, nil) // GC wait
	return nil, s.parseProposeCtxErr(cctx.Err(), start)
case <-s.done:
	return nil, ErrStopped
}
  • 等待逻辑
    1. 通道 ch:正常返回应用结果。
    2. 上下文超时:处理超时错误,并清理等待通道。
    3. 服务关闭:直接返回停止错误。
  • 触发机制 :使用 Trigger 清理通道,避免资源泄露。

8. 性能指标统计

  • proposalsPending.Inc():增加当前挂起的提案计数。
  • proposalsFailed.Inc():统计失败提案次数。

关键逻辑总结

processInternalRaftRequestOnce 方法的核心逻辑可分为以下几个阶段:

  1. 预检查:检查索引状态、请求大小和用户认证。
  2. 请求处理:序列化请求并将其提交到 Raft 模块。
  3. 结果等待:通过通道或超时控制获取提案的处理结果。

流程图

超出限制 正常 超出限制 正常 正常 超时 服务关闭 收到内部请求 检查已应用索引与已提交索引 返回 ErrTooManyRequests 生成请求头并检查认证信息 检查请求大小 返回 ErrRequestTooLarge 注册等待通道 调用 Raft 提案 等待结果 返回提案结果 返回超时错误 返回 ErrStopped

zz总结

processInternalRaftRequestOnce 是 etcd 服务端处理内部 Raft 请求的重要方法,它结合了请求校验、身份认证、Raft 提案以及结果返回的完整逻辑链条。理解其实现,可以帮助我们深入掌握 etcd 的核心一致性协议和服务端处理流程。

相关推荐
张声录18 天前
【ETCD】【源码阅读】深入探索 ETCD 源码:了解 `Range` 操作的底层实现
java·数据库·etcd
爱晒太阳的小老鼠8 天前
apisix的etcd使用
java·etcd
可以吧可以吧12 天前
Docker Compose etcd 服务
docker·容器·etcd
dengjiayue14 天前
分布式锁 Redis vs etcd
redis·分布式·etcd
ZHOU西口24 天前
微服务实战系列之玩转Docker(十九)
docker·云原生·https·etcd·cfssl
张声录11 个月前
【ETCD】【实操篇(十七)】 etcd 集群定期维护指南
数据库·etcd
张声录11 个月前
【ETCD】【实操篇(十九)】ETCD基准测试实战
java·数据库·etcd
张声录11 个月前
【ETCD】【实操篇(十四)】etcd 集群备份与还原指南
数据库·etcd
bennybi1 个月前
基于Docker的ETCD分布式集群
分布式·docker·etcd
张声录11 个月前
【ETCD】【实操篇(二十)】浅谈etcd集群管理的艺术:从两阶段配置到灾难恢复的设计原则
数据库·etcd