etcd 源码分析之storage

MemoryStorage

在内存上维护状态信息、快照数据、Entry

type MemoryStorage struct {
	// Protects access to all fields. Most methods of MemoryStorage are
	// run on the raft goroutine, but Append() is run on an application
	// goroutine.
	sync.Mutex
    //状态信息(当前任期,当前节点投票给了谁,已提交的entry记录的位置)
	hardState pb.HardState
	//快照信息
	snapshot  pb.Snapshot
	// ents[i] has raft log position i+snapshot.Metadata.Index
	//快照数据之后的所有entry记录
	ents []pb.Entry
}

type HardState struct {
    //当前任期
	Term             uint64 `protobuf:"varint,1,opt,name=term" json:"term"`
	//投票给了谁
	Vote             uint64 `protobuf:"varint,2,opt,name=vote" json:"vote"`
	//已提交的位置
	Commit           uint64 `protobuf:"varint,3,opt,name=commit" json:"commit"`
	XXX_unrecognized []byte `json:"-"`
}

InitialState方法直接返回hardstate和快照里元数据记录的confstate实例

func (ms *MemoryStorage) InitialState() (pb.HardState, pb.ConfState, error) {
	return ms.hardState, ms.snapshot.Metadata.ConfState, nil
}

LastIndex和FirstIndex分别返回ents数组的最后和第一条

// LastIndex implements the Storage interface.
func (ms *MemoryStorage) LastIndex() (uint64, error) {
	ms.Lock()
	defer ms.Unlock()
	return ms.lastIndex(), nil
}

func (ms *MemoryStorage) lastIndex() uint64 {
	return ms.ents[0].Index + uint64(len(ms.ents)) - 1
}

// FirstIndex implements the Storage interface.
func (ms *MemoryStorage) FirstIndex() (uint64, error) {
	ms.Lock()
	defer ms.Unlock()
	return ms.firstIndex(), nil
}

func (ms *MemoryStorage) firstIndex() uint64 {
	return ms.ents[0].Index + 1
}

ApplySnapshot
更新快照数据,将snapshot实例保存到memorystorage中

func (ms *MemoryStorage) ApplySnapshot(snap pb.Snapshot) error {
	ms.Lock()
	defer ms.Unlock()

	//判断index 新旧谁大,新的大才替换
	msIndex := ms.snapshot.Metadata.Index
	snapIndex := snap.Metadata.Index
	if msIndex >= snapIndex {
		return ErrSnapOutOfDate
	}

	ms.snapshot = snap
	ms.ents = []pb.Entry{{Term: snap.Metadata.Term, Index: snap.Metadata.Index}}
	return nil
}

Append
向快照添加数据

func (ms *MemoryStorage) Append(entries []pb.Entry) error {
    //常规检查
	if len(entries) == 0 {
		return nil
	}
	ms.Lock()
	defer ms.Unlock()
	first := ms.firstIndex()
	//获取待添加的最后一条的index值
	last := entries[0].Index + uint64(len(entries)) - 1
	// shortcut if there is no new entry.
	if last < first {
		return nil
	}
	// truncate compacted entries
	//截断已经记入snapshot中的
	if first > entries[0].Index {
		entries = entries[first-entries[0].Index:]
	}
    //计算切片一条和first的差
	offset := entries[0].Index - ms.ents[0].Index
	switch {
	case uint64(len(ms.ents)) > offset:
		ms.ents = append([]pb.Entry{}, ms.ents[:offset]...)
		ms.ents = append(ms.ents, entries...)
	case uint64(len(ms.ents)) == offset:
		ms.ents = append(ms.ents, entries...)
	default:
		raftLogger.Panicf("missing log entry [last: %d, append at: %d]",
			ms.lastIndex(), entries[0].Index)
	}
	return nil
}

//ps : 有点绕,为了不重复append

entries
查询方法,查询指定范围的entry

// Entries implements the Storage interface.
func (ms *MemoryStorage) Entries(lo, hi, maxSize uint64) ([]pb.Entry, error) {
	ms.Lock()
	defer ms.Unlock()
	offset := ms.ents[0].Index
	//常规判断
	if lo <= offset {
		return nil, ErrCompacted
	}
	if hi > ms.lastIndex()+1 {
		raftLogger.Panicf("entries' hi(%d) is out of bound lastindex(%d)", hi, ms.lastIndex())
	}
	// only contains dummy entries.
	if len(ms.ents) == 1 {
		return nil, ErrUnavailable
	}
    //获取下标的数据
	ents := ms.ents[lo-offset : hi-offset]
	//limitsize把超过大小的数据剔除
	return limitSize(ents, maxSize), nil
}

//limitsize把超过大小的数据剔除
func limitSize(ents []pb.Entry, maxSize uint64) []pb.Entry {
	if len(ents) == 0 {
		return ents
	}
	size := ents[0].Size()
	var limit int
	for limit = 1; limit < len(ents); limit++ {
		size += ents[limit].Size()
		if uint64(size) > maxSize {
			break
		}
	}
	return ents[:limit]
}

term
返回指定的index对应的ents值

// Term implements the Storage interface.
func (ms *MemoryStorage) Term(i uint64) (uint64, error) {
	ms.Lock()
	defer ms.Unlock()
	offset := ms.ents[0].Index
	if i < offset {
		return 0, ErrCompacted
	}
	if int(i-offset) >= len(ms.ents) {
		return 0, ErrUnavailable
	}
	return ms.ents[i-offset].Term, nil
}

ents压缩 CreateSnapshot–Compact
CreateSnapshot(更新快照信息)

func (ms *MemoryStorage) CreateSnapshot(i uint64, cs *pb.ConfState, data []byte) (pb.Snapshot, error) {
	ms.Lock()
	defer ms.Unlock()
	//常规检查,要比当前snapshot的index小,抛出异常
	if i <= ms.snapshot.Metadata.Index {
		return pb.Snapshot{}, ErrSnapOutOfDate
	}

	offset := ms.ents[0].Index
	//比ms.ent的last大,抛出异常
	if i > ms.lastIndex() {
		raftLogger.Panicf("snapshot %d is out of bound lastindex(%d)", i, ms.lastIndex())
	}

	ms.snapshot.Metadata.Index = i
	ms.snapshot.Metadata.Term = ms.ents[i-offset].Term
	if cs != nil {
		ms.snapshot.Metadata.ConfState = *cs
	}
	ms.snapshot.Data = data
	return ms.snapshot, nil
}

Compact(删除ents中指定索引之前的数据)

func (ms *MemoryStorage) Compact(compactIndex uint64) error {
	ms.Lock()
	defer ms.Unlock()
	offset := ms.ents[0].Index
	//常规检查
	if compactIndex <= offset {
		return ErrCompacted
	}
	//常规检查
	if compactIndex > ms.lastIndex() {
		raftLogger.Panicf("compact %d is out of bound lastindex(%d)", compactIndex, ms.lastIndex())
	}
    //切片操作咯
	i := compactIndex - offset
	ents := make([]pb.Entry, 1, 1+uint64(len(ms.ents))-i)
	ents[0].Index = ms.ents[i].Index
	ents[0].Term = ms.ents[i].Term
	ents = append(ents, ms.ents[i+1:]...)
	ms.ents = ents
	return nil
}
<think>好的,我现在需要回答用户关于如何阅读etcd源码以及推荐相关资料的问题。首先,我得回忆一下自己了解的etcd相关知识,确保信息准确。 用户的问题分为两部分:如何进行etcd源码阅读,以及推荐的资料或网站。我需要先理清回答的结构,确保逻辑清晰。先从etcd的基本概念开始,说明其重要性,这样用户能明白为什么需要深入源码。 接下来是源码阅读的步骤。应该分阶段进行,比如先了解基础架构,再深入核心模块。需要提到Raft协议,因为etcd依赖它实现一致性,这部分是关键。然后是存储模块、网络通信和客户端交互等,这些都是etcd的核心组成部分。每个模块需要简要说明重点,帮助用户有的放矢。 在推荐资料方面,官方文档是首要推荐的,因为最权威。GitHub仓库里的文档和代码示例很重要。书籍方面,可以推荐《云原生分布式存储基石:etcd深入解析》,这本书可能对理解内部机制有帮助。另外,博客和社区资源也是很好的补充,比如CNCF的案例研究和官方博客。论文方面,Raft的原始论文是必读的,用户可能需要了解理论基础。 还需要考虑用户可能的实践需求,比如调试和测试方法。建议使用Go的调试工具,运行测试案例,甚至参与开源项目,这样能加深理解。同时,注意事项部分要提醒用户先掌握Go语言和分布式系统基础,避免直接陷入代码细节而无法理解整体结构。 现在要检查是否有遗漏点。比如,是否提到了工具推荐?比如Delve调试器,或者如何运行测试。可能还需要强调从入口开始,比如main函数,或者关键接口,这样用户有切入点。 另外,资料推荐部分是否有合适的链接?虽然不能提供超链接,但可以给出名称,用户自行搜索。比如CNCF网站、GitHub地址等。确保推荐的资源是当前有效且被广泛认可的。 最后,整个回答需要保持口语化,避免使用过于技术化的术语,但又要准确。结构分点说明,使用标题和分段,让用户容易阅读。同时,注意使用用户要求的格式,比如数学公式的正确表示,但这个问题中可能涉及较少,主要是在算法部分,如果有提到的话需要注意。 总结一下,回答的结构应该是:简介etcd源码阅读步骤(分阶段),推荐资料(分类型),注意事项和实践建议。确保每个部分都简明扼要,信息准确,帮助用户系统地进行源码学习。</think>etcd 是一款高可用的分布式键值存储系统,广泛用于 Kubernetes 等服务协调场景。阅读其源码是理解其分布式一致性、存储引擎和网络通信机制的重要途径。以下是源码阅读的分步指南和推荐资源: --- ### **一、源码阅读步骤** #### 1. **前置知识储备** - **掌握 Go 语言**:etcd 使用 Go 实现,需熟悉 Goroutine、Channel 等并发模型。 - **理解分布式系统基础**:如 CAP 定理、一致性协议(Raft)、分布式锁等。 - **熟悉 etcd 核心功能**:通过官方文档了解其 API、租约(Lease)、Watch 机制等。 #### 2. **从核心模块切入** - **Raft 协议实现**(`etcd/raft` 目录) 核心代码在 `raft/raft.go`,重点关注 Leader 选举、日志复制和状态机应用逻辑。 ```go // 示例:Raft 状态机处理日志条目 func (r *raft) Step(m pb.Message) error { switch m.Type { case pb.MsgProp: // 处理提案 case pb.MsgApp: // 处理日志追加 } } ``` - **存储引擎**(`etcd/server/storage` 目录) - 内存索引(BoltDB 的键值存储)。 - 数据持久化(WAL 日志和快照机制)。 - **网络通信**(`etcd/clientv3` 和 `etcd/etcdserver/api`) 分析 gRPC 接口定义(如 `rpc.proto`)和客户端-服务端交互流程。 #### 3. **调试与验证** - **运行单节点集群**:通过 `etcd --config-file=etcd.conf.yml` 启动本地调试。 - **单元测试**:执行 `go test -v ./raft/...` 验证 Raft 模块逻辑。 - **使用 Delve 调试**:断点跟踪关键流程(如 Leader 选举)。 --- ### **二、推荐资料与工具** #### 1. **官方资源** - [etcd 官方文档](https://etcd.io/docs/):功能详解、API 定义和配置指南。 - [GitHub 仓库](https://github.com/etcd-io/etcd):直接阅读源码和代码注释。 - [Raft 论文](https://raft.github.io/raft.pdf):理解 etcd 依赖的一致性协议。 #### 2. **书籍与博客** - 《**云原生分布式存储基石:etcd 深入解析**》:详细分析 etcd 架构与源码。 - [etcd 官方博客](https://etcd.io/blog/):版本更新解读和性能优化案例。 - [CNCF 案例研究](https://www.cncf.io/case-studies/):了解 etcd 在生产环境的应用场景。 #### 3. **辅助工具** - **etcdctl**:通过命令行操作 etcd,观察数据变化(如 `etcdctl watch /key`)。 - **WAL 日志分析工具**:直接解析二进制日志,验证数据持久化流程。 --- ### **三、注意事项** 1. **避免过早深入细节**:先梳理模块交互关系(如 Raft 模块如何驱动存储引擎)。 2. **关注 Issue 和 PR**:GitHub 的 Issue 列表和合并的 PR 可揭示设计决策。 3. **动手实践**:修改源码添加日志输出,或实现一个简单的 Raft 状态机。 --- ### **四、学习路径示例** 1. 从 `main.go` 入口分析服务启动流程。 2. 追踪一个写请求(如 `Put`)的完整生命周期:经 gRPC 接口 → Raft 提案 → 存储落地。 3. 研究故障恢复机制:节点重启后如何通过 WAL 日志恢复状态。 通过以上方法,可逐步掌握 etcd 的设计精髓,并应用于分布式系统开发或性能优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值