etcd是什么
存储:Kubernetes集群所有的配置信息和状态数据都会被持久化存储在etcd中,包括节点信息、Pods、ReplicaSets、Services、ConfigMaps、Secrets等各类资源对象。
协调:通过Raft协议,etcd集群中的各个节点达成一致,确保任何时候集群状态的变更都是原子性的、一致的,并且在节点故障时能够快速恢复。
调度:Kubernetes的控制面组件正是通过与etcd交互来管理整个集群的运行状态,例如,当用户通过API Server创建或修改资源时,这些更改首先会被记录到etcd中,然后由controller manager和scheduler等组件根据etcd中的数据来执行相应的集群操作。
模块
算法层(SDK) ./raft/*
应用层(示例) ./contrib/raftexample/*
数据结构定义 ./raftraftp
ectd关键词
etcd是一个强一致性的分布式键值对存储系统,底层基于raft协议保证分布式的一致性和可用性
algorithm module/算法模块
内聚了raft共识机制核心模块,代码层以sdk形式被应用层引入,启动时以独立的goroutine存在,与应用模块通过channel异步通信,决定了应用模块什么时候做以及该做什么
application module/应用模块
聚合了etcd存储通信能力模块,启动时是raft节点的主goruntine,负责与客户通信,与算法模块交互,与算法模块通过channel异步通信等职责
node/算法节点
raft节点在算法层的抽象也是应用层与算法层的交互入口
raft node/应用节点
是raft节点在应用层的抽象,内部拥有node,同时提供客户端请求,以及与集群其他节点通信的能力
transport/网络模块
etcd中的网络模块,为raft节点集群内部通信提供服务,应用层引入,独立于算法模块
proposal/提议
两阶段提交中的第一阶段
commit/提交
两阶段中的第二阶段
apply/应用
将已经提交的日志应用到数据状态机,使得写请求生效
data state machine/数据状态机
raft节点用于存储数据的机制,是KV数据库
node state machine/节点状态机
etcd实现中raft节点本质是一个大的状态机,任何操作,例如选举,数据的提交,都会封装成消息输入节点状态机,驱动节点状态发生变化
两个channel
应用模块和算法模块是两个互相阻塞监听的channel
graph LR A(Application)--reciveC-->B(Algorithm) A--propC-->B B--readyC-->A A--advanceC-->B A--tickC-->B模块通信每个轮次是有来有回的,例如一次日志处理就包括了prop ready advance的多通道(Channel)间通信
应用模块通道职责
接收响应客户端请求
预写日志持久化
raft节点通信
维护数据状态机
算法模块通道职责
日志两阶段提交
节点角色管理
选举投票决策
竞选计时
心跳计时
[!quote]
./contrib/raftexample/raft.go中serveChannels函数中的阻塞监听例子如下
for rc.proposeC != nil && rc.confChangeC != nil {
select {
case prop, ok := <-rc.proposeC:
if !ok {
rc.proposeC = nil
} else {
// blocks until accepted by raft state machine
rc.node.Propose(context.TODO(), []byte(prop))
}
case cc, ok := <-rc.confChangeC:
if !ok {
rc.confChangeC = nil
} else {
confChangeCount += 1
cc.ID = confChangeCount
rc.node.ProposeConfChange(context.TODO(), cc)
}
}
}
预写日志EntryType
EntryType是算法模块的一个类型
EntryType就是包含预写日志内容的数据,又被Message类型包含
注意此处包括之后的代码并不完全复原etcd中的源代码,而是抽象的表示
type EntryType int32
const (
//普通日志内容数据
EntryNormal EntryType = 0
//改变配置的数据
EntryConfChange EntryType = 1
)
//...中略
type Entry struct {
//任期
Term uint64
//索引
Index uint64
Type EntryType
//数据内容
Data []byte
}
Message类型
Message就是节点之间传递的信息,前面提到,两个通道的消息是包含多种内容,因此通信的Message的类型是多种的,Message结构体包含的成员数据也是冗余的。
MessageType类型
type MessageType int32
const (
MsgHup MessageType = 0 //通知Leader心跳消失,之后节点会推举自己为leader
MsgBeat MessageType = 1 //Leader心跳
MsgProp MessageType = 2 //已接受到客户端写请求
MsgApp MessageType = 3 //Leader向集群其他节点同步数据
MsgAppResp MessageType = 4 //(该节点是Leader) 发出的MsgApp请求得到响应
MsgVote MessageType = 5 //投票请求
MsgVoteResp MessageType = 6 //投票请求响应
//...
MsgHeartByte MessageType = 8 //leader发送心跳
MsgHeartByteResp MessageType = 9 //follower心跳响应
//...
MsgReadIndex MessageType = 15 //客户端读请求
MsgReadIndexResp MessageType = 16 //客户端读请求响应、
MsgPreVote MessageType = 17 /*见详解(1)*/
MsgPreVoteResp MessageType = 18 /*见详解(1)*/
)
- MsgPreVote为预选举,是为了解决在一个分布式系统中,不同的节点之间由于网络故障或延迟导致无法正常通信。在这种情况下,可能会出现多个副本都认为自己是领导者,或者无法获得多数票数导致的问题。预选举是事先发出网络请求,保证自己的网络不存在问题。
Message类型
type Message struct {
Type MessageType
To uint64 //发往节点
From uint64 //发送节点
Term uint64 //任期
LogTerm uint64 /*见详解(1)*/
Index uint64 /*见详解(1)*/
Entries []Entry //日志内容
Commit uint64 //已经提交的最新的日志索引
Snapshot Snapshot
Reject bool //请求是否通过,也用于投票,返回是否同意发起人成为leader
RejectHint uint64 //follower发现日志落后,向leader发出拒绝请求,并要求同步落后的数据
Context []byte
}
- Leader和Follower同步预写日志,为了防止Follower日志超前或落后现象,每个日志都有索引,同时Leader发出的日志会标记索引和前一篇日志任期,如果Follower前一篇的日志的Term与LogTerm不匹配,则会删去不匹配日志,LogTerm标志前面日志的任期,Index标志前面日志的索引
raftLog类型
算法模块通过<-readyC提交给应用模块,预写日志从内存到磁盘,应用模块再通过<-advanceC通知算法模块已经写入磁盘,然后算法模块会将日志状态改变为stable.
算法模块通过raftLog类型查询所有日志状态。
raftLog类型
type raftLog struct {
// 保存最后一次快照之后提交的数据
storage Storage
//保存没有持久化的数据和快照这些数据最终会保存到storage中
unstable unstable
// committed is the highest log position that is known to be in
// stable storage on a quorum of nodes.
/*见详解(1)*/
committed uint64
// applied is the highest log position that the application has
// been instructed to apply to its state machine.
// Invariant: applied <= committed
/*见详解(2)*/
applied uint64
logger Logger
}
-
已知被提交到足够数量节点上的稳定存储中的最高日志位置。
highest log position:指的是日志序列中的一个索引或者偏移量,这个位置代表了最新的已处理事务或事件。
stable storage:通常指能够持久化存储数据且即使在节点重启后仍能保持不变的存储介质,比如磁盘。
quorum of nodes:在分布式系统中,为了保证数据的一致性和可用性,通常会使用法定人数(quorum)机制。这意味着只有当一定数量(超过半数或其他配置的数量)的节点确认收到并存储了一个日志条目时,该条目才被认为是“已提交” -
highest log position that the application has been instructed to apply:applied 变量表示应用(或状态机)已接收到指令并应用于其状态机的最高日志位置。这意味着所有小于等于 applied 位置的日志条目都已经在应用程序的状态机中执行完毕,影响了系统的当前状态。
state machine:在分布式系统中,尤其是在复制状态机模型下,每个节点都维护一个状态机来处理接收到的日志条目,并据此更新自身的状态。
Invariant: applied <= committed:表明任何时候 applied 的值都不能大于 committed。这是因为只有当一个日志条目被提交(即在法定数量节点上稳定存储)后,才能安全地应用到状态机中。这个不变式保证了即使在出现故障、网络分区或其他异常情况下,状态机也不会基于未完全确认的日志条目进行状态变更,从而确保了数据一致性。
unstable类型
type unstable struct {
// the incoming unstable snapshot, if any.
/*见详解(1)*/
snapshot *pb.Snapshot
// 没有写入磁盘的数据
entries []pb.Entry
//entries切片中数据的起始索引
offset uint64
//...
}
- 这个注释通常会出现在日志管理和状态机复制的相关代码中,具体指代的是尚未完全处理并应用到状态机中的“不稳定的”(incoming unstable)快照。在Raft等算法中,快照是用来压缩日志、减少存储开销,并快速同步节点状态的一种方法。
Storage接口
Storage接口出现在raftLog类型中,提供了日志查询能力
type Storage interface {
//返回保存的初始状态
InitialState() (pb.HardState, pb.ConfState, error)
//返回再[lo,hi)之间并且大于maxSize的Entry切片
Entries(lo, hi, maxSize uint64) ([]pb.Entry, error)
//传入一个索引,返回索引对应的任期
//错误类型包括
//ErrCompacted 传入的索引找不到,已经被压缩成快照
//ErrUnavaiable传入的索引值大于当前的最大索引
Term(i uint64) (uint64, error)
//最后一条日志的索引
LastIndex() (uint64, error)
// FirstIndex returns the index of the first log entry that is
// possibly available via Entries (older entries have been incorporated
// into the latest Snapshot; if storage only contains the dummy entry the
// first log entry is not available).
/*见详解(1)*/
FirstIndex() (uint64, error)
// ...略
}
- 该函数 FirstIndex() 返回首个索引值。一旦快照被创建,包含在快照中的所有先前的日志条目将不再直接可从日志存储中获取,因为它们已经被整合进最新的快照中。
如果当前存储仅包含一个占位符或虚拟日志条目(dummy entry),那么这意味着没有可供查询的实际日志数据,因此第一个有效日志条目的索引是不可用的。在这种情况下,FirstIndex() 函数可能返回无效的索引或者是系统的下一个预期日志条目的索引。
Ready结构体
算法层处理需求后,发往应用模块的信息
算法模块同步通知应用模块的需要做什么工作的结构体Ready,Ready被ReadyCh通道传送
type Ready struct {
// Leader身份,节点状态,只暂时保存在内存中
*SoftState
// 包括任期,投票归属(作为一个非leader,自己将票投给了谁),提交日志索引
pb.HardState
// 该函数用于当节点的应用索引大于ReadState中的索引时,可以用于本地处理线性一致性读请求。
//当Raft接收到msgReadIndex消息时,会返回readState。返回的readState仅对请求读取的请求有效。
ReadStates []ReadState
// 需要应用模块持久化的预写日志
Entries []pb.Entry
//...
// 已经提交的预写日志,需要应用模块应用到状态机
CommittedEntries []pb.Entry
// 待发送信息
Messages []pb.Message
}
标签:uint64,etcd,goroutine,MessageType,状态机,模块,raft,日志,节点
From: https://www.cnblogs.com/ling-2945/p/18117147