本博文将详细介绍分布式注册中心etcd的原理与实践。将与其他注册中心一起对比,分别区分其他注册中心的优势和不同。
etcd的概念ETCD 是一个高可用的分布式键值数据库,可用于服务发现。ETCD 采用raft 一致性算法,基于 Go语言实现。etcd作为一个高可用键值存储系统,天生就是为集群化而设计的。由于Raft算法在做决策时需要多数节点的投票,所以etcd一般部署集群推荐奇数个节点,推荐的数量为3、5或者7个节点构成一个集群。
etcd的架构HTTP Server:接受客户端发出的 API 请求以及其它 etcd 节点的同步与心跳信息请求。
Store:用于处理 etcd 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件理与执行等等,是etcd 对用户提供的大多数 API 功能的具体实现。
Raft:强一致性算法的具体实现,是 etcd 的核心算法。
WAL(Write Ahead Log,预写式日志):是 etcd 的数据存储方式,etcd 会在内存中储存所有数据状态以及节点的索引,此外,etcd 还会通过 WAL 进行持久化存储。WAL 中,所有的数据提交前会事先记录日志。
- Snapshot 是为了防止数据过多而进行的状态快照;
- Entry 表示存储的具体日志内容。
通常,一个用户的请求发送过来,会经由 HTTP Server 转发给 Store 进行具体的事务处理,如果涉及到节点数据的修改,则交给 Raft 模块进行状态的变更、日志的记录,然后再同步给别的 etcd 节点以确认数据提交,最后进行数据的提交,再次同步。
etcd的架构原理一个etcd节点运行以后,有3个通道接收外界消息,以kv数据的增删改查请求处理为例,介绍这3个通道的工作机制。
- client的http调用:会通过注册到http模块的keysHandler的ServeHTTP方法处理。解析好的消息调用EtcdServer的Do()方法处理。(图中2)
- client的grpc调用:启动时会向grpc server注册quotaKVServer对象,quotaKVServer是以组合的方式增强了kvServer这个数据结构。grpc消息解析完以后会调用kvServer的Range、Put、DeleteRange、Txn、Compact等方法。kvServer中包含有一个RaftKV的接口,由EtcdServer这个结构实现。所以最后就是调用到EtcdServer的Range、Put、DeleteRange、Txn、Compact等方法。(图中1)
- 节点之间的grpc消息:每个EtcdServer中包含有Transport结构,Transport中会有一个peers的map,每个peer封装了节点到其他某个节点的通信方式。包括streamReader、treamWriter等,用于消息的发送和接收。streamReader中有recvc和propc队列streamReader处理完接收到的消息会将消息推到这连个队列中。由peer去处理,peer调用raftNode的Process方法处理消息。(图中3、4)
对于客户端消息,调用到EtcdServer处理时,一般都是先注册一个等待队列,调用node的Propose方法,然后用等待队列阻塞等待消息处理完成。Propose方法会往propc队列中推送一条MsgProp消息。
对于节点间的消息,raftNode的Process是直接调用node的step方法,将消息推送到node的recvc或者propc队列中。可以看到,所有消息这时候都到了node结构中的recvc队列或者propc队列中。(图中5)
node处理消息node启动时会启动一个协程,处理node的各个队列中的消息,当然也包括recvc和propc队列。从propc和recvc队列中拿到消息,会调用raft对象的Step方法,raft对象封装了raft的协议数据和操作,其对外的Step方法是raft协议状态机的步进方法。
当接收到消息以后,根据协议类型、Term字段做相应的状态改变处理,或者对选举请求做相应处理。对于一般的kv增删改查数据请求消息,会调用内部的step方法。内部的step方法是一个可动态改变的方法,将随状态机的状态变化而变化。当状态机处于leader状态时,该方法就是stepLeader;
当状态机处于follower状态时,该方法就是stepFollower;当状态机处于Candidate状态时,该方法就是stepCandidate。leader状态会直接处理MsgProp消息。将消息中的日志条目存入本地缓存中,follower则会直接将MsgProp消息转发给leader,转发的过程是将先将消息推送到raft的msgs数组中。
node处理完消息以后,要么生成了缓存中的日志条目,要么生成了将要发送出去的消息。缓存中的日志条目需要进一步处理(比如同步和持久化),而消息需要进一步处理发送出去。处理过程还是在node的这个协程中,在循环开始会调用newReady,将需要进一步处理的日志和需要发送出去的消息,以及状态改变信息,都封装在一个Ready消息中。Ready消息会推行到readyc队列(图中5)。
raftNode的处理raftNode的start()方法另外启动了一个协程,处理readyc队列(图中6)。取出需要发送的message,调用transport的Send方法并将其发送出去(图中4)。调用storage的Save方法持久化存储日志条目或者快照(图中9、10),更新key-value 缓存。
另外需要将已经同步好的日志应用到状态机中,让状态机更新状态和kv存储,通知等待请求完成的客户端。因此需要将已经确定同步好的日志、快照等信息封装在一个apply消息中推送到applyc队列。
EtcdServer的apply处理EtcdServer会处理这个applyc队列。会将snapshot和entries都apply到kv存储中去(图中8)。最后调用applyWait的Trigger,唤醒客户端请求的等待线程,返回客户端的请求。
Raft协议算法原理Raft算法为目标设计的一致性共识算法,涉及到共识算法就必然会提到Paxos,但是Paxos的实现和理解起来都非常复杂。Raft协议采用分治的思想,把分布式协同的问题分为3个问题:
- 选举: 一个新的集群启动时,或者老的leader故障时,会选举出一个新的leader。
- 日志同步: leader必须接受客户端的日志条目并且将他们同步到集群的所有机器。
- 安全: 保证任何节点只要在它的状态机中生效了一条日志,就不会在相同的key上生效另一条日志条目。
一个Raft集群一般包含数个节点,典型的是5个,这样可以承受其中2个节点故障。每个节点实际上就是维护一个状态机,节点在任何时候都处于以下三个状态中的一个。
- leader:负责日志的同步管理,处理来自客户端的请求,与Follower保持这heartBeat的联系
- follower:刚启动时所有节点为Follower状态,响应Leader的日志同步请求,响应Candidate的请求,把请求到Follower的事务转发给Leader
- candidate:负责选举投票,Raft刚启动时由一个节点从Follower转为Candidate发起选举,选举出Leader后从Candidate转为Leader状态
节点启动以后,首先都是follower状态,在follower状态下,会有一个选举超时时间的计时器(这个时间是在配置的超时时间基础上加一个随机的时间得来的)。如果在这个时间内没有收到leader发送的心跳包,则节点状态会变成candidate状态,也就是变成了候选人,候选人会循环广播选举请求,如果超过半数的节点同意选举请求,则节点转化为leader状态。如果在选举过程中,发现已经有了leader或者有更高的任期值的选举信息,则自动变成follower状态。处于leader状态的节点如果发现有更高任期值的leader存在,则也是自动变成follower状态。
Raft把时间划分为任期(Term)(如下图所示),任期是一个递增的整数,一个任期是从开始选举leader到leader失效的这段时间。有点类似于一届总统任期,只是它的时间是不一定的,也就是说只要leader工作状态良好,它可能成为一个独裁者,一直不下台。
是整个etcd节点的功能的入口,包含etcd节点运行过程中需要的大部分成员。
type EtcdServer struct {
// 当前正在发送的snapshot数量
inflightSnapshots int64
//已经apply到状态机的日志index
appliedIndex uint64
//已经提交的日志index,也就是leader确认多数成员已经同步了的日志index
committedIndex uint64
//已经持久化到kvstore的index
consistIndex consistentIndex
//配置项
Cfg *ServerConfig
//启动成功并注册了自己到cluster,关闭这个通道。
readych chan struct{}
//重要的数据结果,存储了raft的状态机信息。
r raftNode
//满多少条日志需要进行snapshot
snapCount uint64
//为了同步调用情况下让调用者阻塞等待调用结果的。
w wait.Wait
//下面3个结果都是为了实现linearizable 读使用的
readMu sync.RWMutex
readwaitc chan struct{}
readNotifier *notifier
//停止通道
stop chan struct{}
//停止时关闭这个通道
stopping chan struct{}
//etcd的start函数中的循环退出,会关闭这个通道
done chan struct{}
//错误通道,用以传入不可恢复的错误,关闭raft状态机。
errorc chan error
//etcd实例id
id types.ID
//etcd实例属性
attributes membership.Attributes
//集群信息
cluster *membership.RaftCluster
//v2的kv存储
store store.Store
//用以snapshot
snapshotter *snap.Snapshotter
//v2的applier,用于将commited index apply到raft状态机
applyV2 ApplierV2
//v3的applier,用于将commited index apply到raft状态机
applyV3 applierV3
//剥去了鉴权和配额功能的applyV3
applyV3Base applierV3
//apply的等待队列,等待某个index的日志apply完成
applyWait wait.WaitTime
//v3用的kv存储
kv mvcc.ConsistentWatchableKV
//v3用,作用是实现过期时间
lessor lease.Lessor
//守护后端存储的锁,改变后端存储和获取后端存储是使用
bemu sync.Mutex
//后端存储
be backend.Backend
//存储鉴权数据
authStore auth.AuthStore
//存储告警数据
alarmStore *alarm.AlarmStore
//当前节点状态
stats *stats.ServerStats
//leader状态
lstats *stats.LeaderStats
//v2用,实现ttl数据过期的
SyncTicker *time.Ticker
//压缩数据的周期任务
compactor *compactor.Periodic
//用于发送远程请求
peerRt http.RoundTripper
//用于生成请求id
reqIDGen *idutil.Generator
// forceVersionC is used to force the version monitor loop
// to detect the cluster version immediately.
forceVersionC chan struct{}
// wgMu blocks concurrent waitgroup mutation while server stopping
wgMu sync.RWMutex
// wg is used to wait for the go routines that depends on the server state
// to exit when stopping the server.
wg sync.WaitGroup
// ctx is used for etcd-initiated requests that may need to be canceled
// on etcd server shutdown.
ctx context.Context
cancel context.CancelFunc
leadTimeMu sync.RWMutex
leadElectedTime time.Time
}
raftNode数据结构
raft状态机,维护raft状态机的步进和状态迁移。
type raftNode struct {
// Cache of the latest raft index and raft term the server has seen.
// These three unit64 fields must be the first elements to keep 64-bit
// alignment for atomic access to the fields.
//状态机当前状态,index代表当前已经apply到状态机的日志index,term是最新日志条目的term,lead是当前的leader id
index uint64
term uint64
lead uint64
//包含了node、storage等重要数据结构
raftNodeConfig
// a chan to send/receive snapshot
msgSnapC chan raftpb.Message
// a chan to send out apply
applyc chan apply
// a chan to send out readState
readStateC chan raft.ReadState
// utility
ticker *time.Ticker
// contention detectors for raft heartbeat message
td *contention.TimeoutDetector
stopped chan struct{}
done chan struct{}
}
node数据结构
包含在raftNode中,是Node接口的实现。里面包含一个协程和多个队列,是状态机消息处理的入口。
type node struct {
//Propose队列,调用raftNode的Propose即把Propose消息塞到这个队列里
propc chan pb.Message
//Message队列,除Propose消息以外其他消息塞到这个队列里
recvc chan pb.Message
//集群配置信息队列,当集群节点改变时,需要将修改信息塞到这个队列里
confc chan pb.ConfChange
//外部通过这个队列获取修改后集群配置信息
confstatec chan pb.ConfState
//已经准备好apply的信息队列
readyc chan Ready
//每次apply好了以后往这个队列里塞个空对象。通知可以继续准备Ready消息。
advancec chan struct{}
//tick信息队列,用于调用心跳
tickc chan struct{}
done chan struct{}
stop chan struct{}
status chan chan Status
logger Logger
}
etcd的特点
实际上,etcd作为一个受到Zookeeper与doozer启发而催生的项目,除了拥有与之类似的功能外,更具有以下4个特点:
- 简单:基于HTTP+JSON的API让你用curl命令就可以轻松使用。
- 安全:可选SSL客户认证机制。
- 快速:每个实例每秒支持一千次写操作。
- 可信:使用Raft算法充分实现了分布式。
- Raft:etcd所采用的保证分布式系统强一致性的算法。
- Node:一个Raft状态机实例。
- Member: 一个etcd实例。它管理着一个Node,并且可以为客户端请求提供服务。
- Cluster:由多个Member构成可以协同工作的etcd集群。
- Peer:对同一个etcd集群中另外一个Member的称呼。
- Client: 向etcd集群发送HTTP请求的客户端。
- WAL:预写式日志,etcd用于持久化存储的日志格式。
- snapshot:etcd防止WAL文件过多而设置的快照,存储etcd数据状态。
- Proxy:etcd的一种模式,为etcd集群提供反向代理服务。
- Leader:Raft算法中通过竞选而产生的处理所有数据提交的节点。
- Follower:竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。
- Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始Leader竞选。
- Term:某个节点成为Leader到下一次竞选开始的时间周期,称为一个Term。
- Index:数据项编号。Raft中通过Term和Index来定位数据。
- 获得了 IANA 认证的端口,2379 用于客户端通信,2380 用于节点通信,与原先的(4001 peers / 7001 clients)共用。
- 每个节点可监听多个广播地址。监听的地址由原来的一个扩展到多个,用户可以根据需求实现更加复杂的集群环境,如一个是公网 IP,一个是虚拟机(容器)之类的私有 IP。
- etcd 可以代理访问 leader 节点的请求,所以如果你可以访问任何一个 etcd 节点,那么你就可以无视网络的拓扑结构对整个集群进行读写操作。
- etcd 集群和集群中的节点都有了自己独特的 ID。这样就防止出现配置混淆,不是本集群的其他 etcd 节点发来的请求将被屏蔽。
- etcd 集群启动时的配置信息目前变为完全固定,这样有助于用户正确配置和启动。
- 运行时节点变化 (Runtime Reconfiguration)。用户不需要重启 etcd 服务即可实现对 etcd 集群结构进行变更。启动后可以动态变更集群配置。
- 重新设计和实现了 Raft 算法,使得运行速度更快,更容易理解,包含更多测试代码。
- Raft 日志现在是严格的只能向后追加、预写式日志系统,并且在每条记录中都加入了 CRC 校验码。
- 启动时使用的 _etcd/* 关键字不再暴露给用户
- 废弃集群自动调整功能的 standby 模式,这个功能使得用户维护集群更困难。
- 新增 Proxy 模式,不加入到 etcd 一致性集群中,纯粹进行代理转发。
- ETCD_NAME(-name)参数目前是可选的,不再用于唯一标识一个节点。
- 摒弃通过配置文件配置 etcd 属性的方式,你可以用环境变量的方式代替。
- 通过自发现方式启动集群必须要提供集群大小,这样有助于用户确定集群实际启动的节点数量。
服务发现(Service Discovery)要解决的是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务如何才能找到对方并建立连接。从本质上说,服务发现就是想要了解集群中是否有进程在监听udp或tcp端口,并且通过名字就可以进行查找和连接。
消息发布与订阅:在分布式系统中,最为适用的组件间通信方式是消息发布与订阅机制。具体而言,即构建一个配置共享中心,数据提供者在这个配置中心发布消息,而消息使用者则订阅他们关心的主题,一旦相关主题有消息发布,就会实时通知订阅者。通过这种方式可以实现分布式系统配置的集中式管理与实时动态更新
- 应用中用到的一些配置信息存放在etcd上进行集中管理。这类场景的使用方式通常是这样的:应用在启动的时候主动从etcd获取一次配置信息,同时,在etcd节点上注册一个Watcher并等待,以后每次配置有更新的时候,etcd都会实时通知订阅者,以此达到获取最新配置信息的目的。
- 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态信息存放在etcd中,供各个客户端订阅使用。使用etcd的
key TTL
功能可以确保机器状态是实时更新的。 - 分布式日志收集系统。这个系统的核心工作是收集分布在不同机器上的日志。收集器通常按照应用(或主题)来分配收集任务单元,因此可以在etcd上创建一个以应用(或主题)命名的目录P,并将这个应用(或主题)相关的所有机器ip,以子目录的形式存储在目录P下,然后设置一个递归的etcd Watcher,递归式地监控应用(或主题)目录下所有信息的变动。这样就实现了在机器IP(消息)发生变动时,能够实时通知收集器调整任务分配。
- 系统中信息需要动态自动获取与人工干预修改信息请求内容的情况。通常的解决方案是对外暴露接口,例如JMX接口,来获取一些运行时的信息或提交修改的请求。而引入etcd之后,只需要将这些信息存放到指定的etcd目录中,即可通过HTTP接口直接被外部访问。
在分布式系统中,为了保证服务的高可用以及数据的一致性,通常都会把数据和服务部署多份,以此达到对等服务,即使其中的某一个服务失效了,也不影响使用。这样的实现虽然会导致一定程度上数据写入性能的下降,但是却能实现数据访问时的负载均衡。因为每个对等服务节点上都存有完整的数据,所以用户的访问流量就可以分流到不同的机器上。
- etcd本身分布式架构存储的信息访问支持负载均衡。etcd集群化以后,每个etcd的核心节点都可以处理用户的请求。所以,把数据量小但是访问频繁的消息数据直接存储到etcd中也是个不错的选择,如业务系统中常用的二级代码表。二级代码表的工作过程一般是这样,在表中存储代码,在etcd中存储代码所代表的具体含义,业务系统调用查表的过程,就需要查找表中代码的含义。所以如果把二级代码表中的小量数据存储到etcd中,不仅方便修改,也易于大量访问。
- 利用etcd维护一个负载均衡节点表。etcd可以监控一个集群中多个节点的状态,当有一个请求发过来后,可以轮询式地把请求转发给存活着的多个节点。类似KafkaMQ,通过Zookeeper来维护生产者和消费者的负载均衡。同样也可以用etcd来做Zookeeper的工作。
这里讨论的分布式通知与协调,与消息发布和订阅有些相似。两者都使用了etcd中的Watcher机制,通过注册与异步通知机制,实现分布式环境下不同系统之间的通知与协调,从而对数据变更进行实时处理。实现方式通常为:不同系统都在etcd上对同一个目录进行注册,同时设置Watcher监控该目录的变化(如果对子目录的变化也有需要,可以设置成递归模式),当某个系统更新了etcd的目录,那么设置了Watcher的系统就会收到通知,并作出相应处理。
- 通过etcd进行低耦合的心跳检测。检测系统和被检测系统通过etcd上某个目录关联而非直接关联起来,这样可以大大减少系统的耦合性。
- 通过etcd完成系统调度。某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台做的一些操作,实际上只需要修改etcd上某些目录节点的状态,而etcd就会自动把这些变化通知给注册了Watcher的推送系统客户端,推送系统再做出相应的推送任务。
- 通过etcd完成工作汇报。大部分类似的任务分发系统,子任务启动后,到etcd来注册一个临时工作目录,并且定时将自己的进度进行汇报(将进度写入到这个临时目录),这样任务管理者就能够实时知道任务进度。
因为etcd使用Raft算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。
- 保持独占,即所有试图获取锁的用户最终只有一个可以得到。etcd为此提供了一套实现分布式锁原子操作CAS(
CompareAndSwap
)的API。通过设置prevExist
值,可以保证在多个节点同时创建某个目录时,只有一个成功,而该用户即可认为是获得了锁。 - 控制时序,即所有试图获取锁的用户都会进入等待队列,获得锁的顺序是全局唯一的,同时决定了队列执行顺序。etcd为此也提供了一套API(自动创建有序键),对一个目录建值时指定为
POST
动作,这样etcd会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用API按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。
mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.backup
mv /etc/yum.repos.d/epel-testing.repo /etc/yum.repos.d/epel-testing.repo.backup
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
安装配置存储etcd
[root@node1 ~]# yum -y install etcd
[root@node2 ~]# yum -y install etcd
[root@master ~]# yum -y install etcd
[root@master ~]# etcd --version
etcd Version: 3.2.22
Git SHA: 1674e68
Go Version: go1.9.4
Go OS/Arch: linux/amd64
配置相关文件
master节点
[root@master ~]# egrep -v "^$|^#" /etc/etcd/etcd.confETCD_DATA_DIR="/var/lib/etcd/default.etcd" #etcd数据保存目录
ETCD_LISTEN_PEER_URLS="http://192.168.10.5:2380" #集群内部通信使用的URL
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379" #供外部客户端使用的url
ETCD_NAME="etcd01" #etcd实例名称
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.10.5:2380" #广播给集群内其他成员访问的URL
ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379" #广播给外部客户端使用的url
ETCD_INITIAL_CLUSTER="etcd01=http://192.168.10.5:2380,etcd02=http://192.168.10.6:2380,etcd03=http://192.168.10.7:2380" #初始集群成员列表
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" #集群的名称
ETCD_INITIAL_CLUSTER_STATE="new" #初始集群状态,new为新建集群
node-1节点
[root@node1 etcd]# egrep -v "^$|^#" /etc/etcd/etcd.conf
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.10.6:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="etcd02"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.10.6:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_INITIAL_CLUSTER="etcd01=http://192.168.10.5:2380,etcd02=http://192.168.10.6:2380,etcd03=http://192.168.10.7:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="exist"
node-2
[root@node2 ~]# egrep -v "^$|^#" /etc/etcd/etcd.conf
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.10.7:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="etcd03"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.10.7:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_INITIAL_CLUSTER="etcd01=http://192.168.10.5:2380,etcd02=http://192.168.10.6:2380,etcd03=http://192.168.10.7:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="exist"
etcd单个节点测试
[root@master etcd]# systemctl start etcd
[root@master etcd]# systemctl status etcd
● etcd.service - Etcd Server
Loaded: loaded (/usr/lib/systemd/system/etcd.service; disabled; vendor preset: disabled)
Active: active (running) since Tue 2019-01-08 17:28:34 CST; 2h 32min ago
Main PID: 114077 (etcd)
Tasks: 7
Memory: 48.7M
CGroup: /system.slice/etcd.service
└─114077 /usr/bin/etcd --name=etcd01 --data-dir=/var/lib/etcd/default.etcd --listen-client-urls=http://0.0.0.0:2379
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 became candidate at term 16
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 received MsgVoteResp from 4c5d727d37966a87 at term 16
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 [logterm: 11, index: 44] sent MsgVote request to 315fd62e577c4037 at term 16
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 [logterm: 11, index: 44] sent MsgVote request to f617da66fb9b90ad at term 16
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 received MsgVoteResp from f617da66fb9b90ad at term 16
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 [quorum:2] has received 2 MsgVoteResp votes and 0 vote rejections
Jan 08 20:00:08 master etcd[114077]: 4c5d727d37966a87 became leader at term 16
Jan 08 20:00:08 master etcd[114077]: raft.node: 4c5d727d37966a87 elected leader 4c5d727d37966a87 at term 16
Jan 08 20:00:09 master etcd[114077]: the clock difference against peer 315fd62e577c4037 is too high [4.285950016s > 1s]
Jan 08 20:00:09 master etcd[114077]: the clock difference against peer f617da66fb9b90ad is too high [4.120954945s > 1s]
[root@master etcd]# netstat -tunlp|grep etcd
tcp 0 0 192.168.10.5:2380 0.0.0.0:* LISTEN 114077/etcd
tcp6 0 0 :::2379 :::* LISTEN 114077/etcd
etcd集群测试
查看集群节点
[root@node1 etcd]# etcdctl member list
315fd62e577c4037: name=etcd03 peerURLs=http://192.168.10.7:2380 clientURLs=http://0.0.0.0:2379 isLeader=false
4c5d727d37966a87: name=etcd01 peerURLs=http://192.168.10.5:2380 clientURLs=http://0.0.0.0:2379 isLeader=true
f617da66fb9b90ad: name=etcd02 peerURLs=http://192.168.10.6:2380 clientURLs=http://0.0.0.0:2379 isLeader=false
查看集群健康状态:
[root@master etcd]# etcdctl cluster-health
member 315fd62e577c4037 is healthy: got healthy result from http://0.0.0.0:2379
member 4c5d727d37966a87 is healthy: got healthy result from http://0.0.0.0:2379
member f617da66fb9b90ad is healthy: got healthy result from http://0.0.0.0:2379
cluster is healthy
设置键值
在一个节点设置值
[root@master etcd]# etcdctl set /test/key "test kubernetes"
test kubernetes
在另一个节点获取值
[root@node1 etcd]# etcdctl get /test/key
test kubernetes
key存在的方式和zookeeper类似,为 /路径/key
设置完之后,其他集群也可以查询到该值
如果dir和key不存在,该命令会创建对应的项
更新键值
[root@master etcd]# etcdctl update /test/key "test kubernetes cluster"
test kubernetes cluster
[root@node1 etcd]# etcdctl get /test/key
test kubernetes cluster
删除键值
[root@master etcd]# etcdctl rm /test/key
PrevNode.Value: test kubernetes cluster
当键不存在时,会报错
[root@node1 etcd]# etcdctl get /test/key
Error: 100: Key not found (/test/key) [15]
博文参考
【深入浅出etcd系列】1. 架构概览 - 知乎
etcd — 架构原理【附源码】_范桂飓51cto_51CTO博客
高可用分布式存储 etcd 的实现原理 - 面向信仰编程
彻底搞懂 etcd 系列文章(一):初识 etcd - 知乎
Raft
分布式架构注册中心etcd源码解析实现原理 - 知乎