ETCD v2 源码分析

本文主要分析了ETCD v2的源码,包括其整体架构、基于raft的一致性协议、内存中的Store结构、请求处理流程,特别是Get、Put操作,以及watch机制。还探讨了线性一致性读(QGET)、TTL管理、CAS操作和key的主动过期。此外,文章讨论了为何选择使用像ETCD这样的协调与配置服务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Etcd is a distributed, consistent key-valuestore for shared configurationand service discovery

ETCD 有 v2和 v3,api 和内部存储都不一样。这里只分析 v2。
基于源码git tag v2.0.0 :git checkout -b version2 v2.0.0

一致性协议使用 raft,raft 协议不在本文叙述范围内。raft 协议相关见ETCD - raft

v2版的 API 文档
etcd API - v2

ETCD 整体架构

在这里插入图片描述
Store 是内存kv数据库。存储所有kv数据。
HTTP Server接受来自客户端的请求。如果是数据变更的请求(PUT,POST,QGET(GET with quorum=true)等),Server 会将请求结构体序列化成一个raft Entry,然后提交Raft传播,等到raft集群达成一致以后(CommittedEntries),Raft先将CommittedEntries(或者快照)存储到WAL中并刷盘(用于重启的时候恢复raft日志),然后见调用
func (s *EtcdServer) apply(es []raftpb.Entry, confState *raftpb.ConfState)
使用CommittedEntries对Store的数据实施变更。每一条Entry都是对Store的操作命令。
EtcdServer接收raft通知的核心代码如下:


func (s *EtcdServer) run() {
   
   
	// TODO: make raft loop a method on raftNode
	for {
   
   
		select {
   
   
		case rd := <-s.r.Ready():
			if rd.SoftState != nil {
   
   
				atomic.StoreUint64(&s.r.lead, rd.SoftState.Lead)
				if rd.RaftState == raft.StateLeader {
   
   
					syncC = s.SyncTicker
					// TODO: remove the nil checking
					// current test utility does not provide the stats
					if s.stats != nil {
   
   
						s.stats.BecomeLeader()
					}
				} else {
   
   
					syncC = nil
				}
			}

			// apply snapshot to storage if it is more updated than current snapi
			if !raft.IsEmptySnap(rd.Snapshot) && rd.Snapshot.Metadata.Index > snapi {
   
   
				if err := s.r.storage.SaveSnap(rd.Snapshot); err != nil {
   
   
					log.Fatalf("etcdserver: save snapshot error: %v", err)
				}
				s.r.raftStorage.ApplySnapshot(rd.Snapshot)
				snapi = rd.Snapshot.Metadata.Index
				log.Printf("etcdserver: saved incoming snapshot at index %d", snapi)
			}

			if err := s.r.storage.Save(rd.HardState, rd.Entries); err != nil {
   
   
				log.Fatalf("etcdserver: save state and entries error: %v", err)
			}
			s.r.raftStorage.Append(rd.Entries)

			s.send(rd.Messages)

			// recover from snapshot if it is more updated than current applied
			if !raft.IsEmptySnap(rd.Snapshot) && rd.Snapshot.Metadata.Index > appliedi {
   
   
				if err := s.store.Recovery(rd.Snapshot.Data); err != nil {
   
   
					log.Panicf("recovery store error: %v", err)
				}
				s.Cluster.Recover()
				appliedi = rd.Snapshot.Metadata.Index
				log.Printf("etcdserver: recovered from incoming snapshot at index %d", snapi)
			}
			// TODO(bmizerany): do this in the background, but take
			// care to apply entries in a single goroutine, and not
			// race them.
			if len(rd.CommittedEntries) != 0 {
   
   
				firsti := rd.CommittedEntries[0].Index
				if firsti > appliedi+1 {
   
   
					log.Panicf("etcdserver: first index of committed entry[%d] should <= appliedi[%d] + 1", firsti, appliedi)
				}
				var ents []raftpb.Entry
				if appliedi+1-firsti < uint64(len(rd.CommittedEntries)) {
   
   
					ents = rd.CommittedEntries[appliedi+1-firsti:]
				}
				if len(ents) > 0 {
   
   
					//处理已经被raft集群提交的请求。
					if appliedi, shouldstop = s.apply(ents, &confState); shouldstop {
   
   
						go s.stopWithDelay(10*100*time.Millisecond, fmt.Errorf("the member has been permanently removed from the cluster"))
					}
				}
			}

			s.r.Advance()

			if appliedi-snapi > s.r.snapCount {
   
   
				log.Printf("etcdserver: start to snapshot (applied: %d, lastsnap: %d)", appliedi, snapi)
				s.snapshot(appliedi, &confState)
				snapi = appliedi
			}
		}
	}
}

raft部分

type raftNode struct {
   
   
	raft.Node

	// config
	snapCount uint64 // number of entries to trigger a snapshot

	// utility
	ticker      <-chan time.Time
	raftStorage *raft.MemoryStorage
	storage     Storage
	// transport specifies the transport to send and receive msgs to members.
	// Sending messages MUST NOT block. It is okay to drop messages, since
	// clients should timeout and reissue their messages.
	// If transport is nil, server will panic.
	transport rafthttp.Transporter

	// Cache of the latest raft index and raft term the server has seen
	index uint64
	term  uint64
	lead  uint64
}

storage接口是etcdserver.Storage,只有etcdserver.storage一种实现。该实现是WAL,磁盘预写式日志,只在raft节点重启恢复的时候用,读出之前存储的快照和entry列表。
raftStorage是rafgLog.storage, rafgLog.storage是 raft.Storage接口,实现只有raft.MemoryStorage一种,用来存储日志和快照, 也就是存储在内存中。

//etcdserver.Storage
type Storage interface {
   
   
	// Save function saves ents and state to the underlying stable storage.
	// Save MUST block until st and ents are on stable storage.
	Save(st raftpb.HardState, ents []raftpb.Entry) error
	// SaveSnap function saves snapshot to the underlying stable storage.
	SaveSnap(snap raftpb.Snapshot) error

	// TODO: WAL should be able to control cut itself. After implement self-controlled cut,
	// remove it in this interface.
	// Cut cuts out a new wal file for saving new state and entries.
	Cut() error
	// Close closes the Storage and performs finalization.
	Close() error
}

//实现Storage
//如果是entry,写入wal.WAL中;
//如果是快照,则快照原信息写入wal.WAL中,快照文件写入snap.Snapshotter表示的地址中。
type storage struct {
   
   
	*wal.WAL
	*snap.Snapshotter
}

type raft struct {
   
   
	pb.HardState

	id uint64

	
<think>好的,我现在需要回答用户的问题:“优秀的go并发项目参考”。用户之前已经询问过关于Go并发优秀源码的问题,现在他们可能希望了解一些具体的开源项目,这些项目在实际中如何应用Go的并发特性。我需要确保回答的结构清晰,分层次,并且提供有用的信息。 首先,回顾之前的回答,用户已经得到了一些标准库和开源项目的例子,比如Etcd和Docker。现在用户可能想要更多的项目参考,或者更详细的项目列表,以便进一步学习。我需要扩展这些例子,并添加更多不同类型的项目,展示Go并发在不同场景下的应用。 接下来,我需要考虑用户的潜在需求。他们可能是开发者,想通过研究实际项目来提升自己的并发编程能力。因此,项目应具有代表性,代码结构清晰,且广泛被认可。同时,项目应涵盖不同领域,如分布式系统、网络服务、数据库等,以展示Go并发的多样性。 我需要收集一些知名的Go项目,特别是那些以并发处理著称的。例如: 1. **Kubernetes**:作为容器编排系统,大量使用Go的并发模型处理集群管理。 2. **CockroachDB**:分布式SQL数据库,利用Go的并发处理分布式事务和一致性。 3. **Tyk**:API网关,处理高并发请求。 4. **InfluxDB**:时间序列数据库,高效处理并发写入和查询。 5. **NSQ**:实时分布式消息平台,使用Go的channel进行消息传递。 6. **Jaeger**:分布式追踪系统,处理并发数据收集和查询。 7. **Go-kit**:微服务工具包,提供并发模式的最佳实践。 8. **Gorilla WebSocket**:处理WebSocket连接的并发管理。 9. **Traefik**:反向代理和负载均衡器,处理高并发连接。 10. **Prometheus**:监控系统,并发抓取和处理指标数据。 每个项目需要简要介绍其并发应用场景和核心机制,例如使用goroutines、channels、sync包的原语等。同时,可以指出源码中的关键文件或模块,帮助用户快速定位学习重点。 还需要注意避免重复之前提到的项目,如Etcd和Docker,但可以补充更多细节。此外,提供学习建议,如如何有效阅读源码,使用工具调试并发问题,以及推荐的资源,帮助用户深入学习。 在结构上,可以按项目类型分类,如分布式系统、数据库、消息队列、网络服务等,每个类别下列举几个项目,并说明其并发实现的特点。这样用户可以根据兴趣或需求选择适合的项目进行研究。 需要确保信息准确,项目确实使用Go并发特性,并且是活跃的开源项目。另外,每个项目的描述要简明扼要,突出其并发方面的亮点,例如如何利用channels进行通信,使用goroutines处理任务,或者如何优化并发性能。 最后,总结部分可以强调通过研究这些项目,用户能学到哪些并发模式或最佳实践,如工作池、扇入扇出、并行处理管道等,并推荐进一步的学习资源或方法。</think>以下是一些优秀的Go并发项目参考,这些项目在并发模型设计和实现上具有示范意义,适合深入学习Go的高并发处理机制: --- ### 一、分布式系统与协调 1. **Kubernetes (k8s)** - **核心并发场景**:协调百万级容器的状态同步、事件处理 - **关键实现**: - `client-go` 中的 `Informer` 机制(`tools/cache/shared_informer.go`) 使用 `Reflector` + `Delta FIFO` 队列监听资源变更,通过 `goroutine` 异步处理事件 - 控制器模式:多个控制器并发运行,通过 `WorkQueue` 实现任务分发 - **源码重点**:`staging/src/k8s.io/client-go/tools/cache/` 2. **CockroachDB** (分布式SQL数据库) - **并发亮点**: - 使用`Raft`共识算法实现多副本一致性(`pkg/storage/raft_transport.go`) - 分布式事务的并发控制(`pkg/sql/execinfra/` 中的并行查询执行) - **关键技术**: - 无锁数据结构优化读写冲突 - `context` 包实现操作级联取消 --- ### 二、高性能网络服务 3. **NSQ** (实时分布式消息平台) - **并发模型**: - 每个消息通道(`Channel`)独立运行处理协程(`nsqd/channel.go`) - 使用 `sync.Pool` 重用内存降低GC压力 - **核心文件**: - `nsqd/protocol_v2.go`:处理TCP连接的读写协程 - `internal/protocol/http.go`:HTTP API的并发请求处理 4. **Traefik** (云原生反向代理) - **并发机制**: - 中间件链的并行处理(`pkg/middlewares/chain.go`) - 动态配置的热加载(`pkg/provider/configuration.go` 中的原子操作替换配置) - **设计模式**: - 使用 `channel` 传递请求上下文 - `sync.Once` 确保单次初始化 --- ### 三、存储与数据处理 5. **InfluxDB** (时间序列数据库) - **并发优化**: - TSM引擎的并行数据压缩(`tsdb/engine/tsm1/compact.go`) - 使用 `mmap` 实现零拷贝并发读取 - **关键技术**: - `RWMutex` 控制分片(Shard)的读写访问 - `go-dedup` 算法优化重复数据删除 6. **Dgraph** (分布式图数据库) - **亮点实现**: - 基于 `Badger` KV存储的并发事务处理(`dgraph/x/`) - 使用 `gRPC` 流式接口实现多节点并行查询 - **源码重点**: - `worker/palimir.go`:分布式查询的并发执行计划 --- ### 四、工具与框架 7. **Go-kit** (微服务工具包) - **并发实践**: - 中间件的并发安全设计(`transport/http/server.go`) - 断路器模式(`circuitbreaker` 包中的状态机原子操作) - **学习重点**: - `endpoint` 链式调用的并发控制 8. **Gorilla WebSocket** - **并发处理**: - 每个WebSocket连接独立读写协程(`conn.go` 中的 `readLoop`/`writeLoop`) - 使用 `sync.Mutex` 保护共享缓冲区 - **抗并发问题**: - 通过 `CloseHandler` 处理并发关闭连接 --- ### 五、学习建议 1. **源码阅读技巧**: - 使用 `go doc` 查看接口定义(如 `go doc sync.Map`) - 结合 `pprof` 分析goroutine运行状态 ```bash go tool pprof -http=:8080 http://localhost:6060/debug/pprof/goroutine ``` 2. **调试工具**: - **Goroutine泄漏检测**:使用 `go.uber.org/goleak` - **竞态检测**:`go build -race` - **调度跟踪**:`GODEBUG=scheddetail=1,schedtrace=1000` 3. **推荐学习路径**: ``` 基础并发原语 → Channel高级模式 → 并发安全设计 → 分布式系统应用 ``` 这些项目展示了Go并发在实践中的典型应用模式: - **通信顺序进程(CSP)**:通过Channel解耦组件 - **工作窃取**:优化任务分配效率 - **无锁编程**:原子操作替代传统锁 - **并发控制**:Context传播与超时管理 通过研究这些项目的源码,可以深入理解如何在高并发场景下平衡性能与正确性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值