ETCD整体机制

etcd 是一个分布式的、可靠的 key-value 存储系统,它适用于存储分布式系统中的关键数据。

etcd 集群中多个节点之间通过Raft算法完成分布式一致性协同,算法会选举出一个主节点作为 leader,由 leader 负责数据的同步与分发。当 leader 出现故障后系统会自动地重新选取另一个节点成为 leader,并重新完成数据的同步。

etcd集群实现高可用主要是基于quorum机制,即:集群中半数以上的节点可用时,集群才可继续提供服务,quorum机制在分布式一致性算法中应用非常广泛,此处不再详细阐述。

raft数据更新和etcd调用是基于两阶段机制:

第一阶段 leader记录log (uncommited);日志复制到follower;follower响应,操作成功,响应客户端;调用者调用leader,leader会将kv数据存储在日志中,并利用实时算法raft进行复制

第二阶段 leader commit;通知follower;当复制给了N+1个节点后,本地提交,返回给客户端,最后leader异步通知follower完成通知

ETCD核心API分析

etcd提供的api主要有kv相关、lease相关及watch,查看其源码可知:

kv相关接口:

type KV interface {
// Put puts a key-value pair into etcd.
// Note that key,value can be plain bytes array and string is
// an immutable representation of that bytes array.
// To get a string of bytes, do string([]byte{0x10, 0x20}).
Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) // Get retrieves keys.
// By default, Get will return the value for "key", if any.
// When passed WithRange(end), Get will return the keys in the range [key, end).
// When passed WithFromKey(), Get returns keys greater than or equal to key.
// When passed WithRev(rev) with rev > 0, Get retrieves keys at the given revision;
// if the required revision is compacted, the request will fail with ErrCompacted .
// When passed WithLimit(limit), the number of returned keys is bounded by limit.
// When passed WithSort(), the keys will be sorted.
Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) // Delete deletes a key, or optionally using WithRange(end), [key, end).
Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) // Compact compacts etcd KV history before the given rev.
Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) // Txn creates a transaction.
Txn(ctx context.Context) Txn
}

主要有Put、Get、Delete、Compact、Do和Txn方法;Put用于向etcd集群中写入消息,以key value的形式存储;Get可以根据key查看其对应存储在etcd中的数据;Delete通过删除key来删除etcd中的数据;Compact 方法用于压缩 etcd 键值对存储中的事件历史,避免事件历史无限制的持续增长;Txn 方法在单个事务中处理多个请求,etcd事务模式为:

if compare

then op

else op

commit

lease相关接口:

type Lease interface {
// Grant creates a new lease.
Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) // Revoke revokes the given lease.
Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) // TimeToLive retrieves the lease information of the given lease ID.
TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) // Leases retrieves all leases.
Leases(ctx context.Context) (*LeaseLeasesResponse, error) // KeepAlive keeps the given lease alive forever. If the keepalive response
// posted to the channel is not consumed immediately, the lease client will
// continue sending keep alive requests to the etcd server at least every
// second until latest response is consumed.
//
// The returned "LeaseKeepAliveResponse" channel closes if underlying keep
// alive stream is interrupted in some way the client cannot handle itself;
// given context "ctx" is canceled or timed out. "LeaseKeepAliveResponse"
// from this closed channel is nil.
//
// If client keep alive loop halts with an unexpected error (e.g. "etcdserver:
// no leader") or canceled by the caller (e.g. context.Canceled), the error
// is returned. Otherwise, it retries.
//
// TODO(v4.0): post errors to last keep alive message before closing
// (see https://github.com/coreos/etcd/pull/7866)
KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) // KeepAliveOnce renews the lease once. The response corresponds to the
// first message from calling KeepAlive. If the response has a recoverable
// error, KeepAliveOnce will retry the RPC with a new keep alive message.
//
// In most of the cases, Keepalive should be used instead of KeepAliveOnce.
KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) // Close releases all resources Lease keeps for efficient communication
// with the etcd server.
Close() error
}

lease 是分布式系统中一个常见的概念,用于代表一个分布式租约。典型情况下,在分布式系统中需要去检测一个节点是否存活的时,就需要租约机制。

Grant方法用于创建一个租约,当服务器在给定 time to live 时间内没有接收到 keepAlive 时租约过期;Revoke撤销一个租约,所有附加到租约的key将过期并被删除;TimeToLive 获取租约信息;KeepAlive 通过从客户端到服务器端的流化的 keep alive 请求和从服务器端到客户端的流化的 keep alive 应答来维持租约;检测分布式系统中一个进程是否存活,可以在进程中去创建一个租约,并在该进程中周期性的调用 KeepAlive 的方法。如果一切正常,该节点的租约会一致保持,如果这个进程挂掉了,最终这个租约就会自动过期,在 etcd 中,允许将多个 key 关联在同一个 lease 之上,可以大幅减少 lease 对象刷新带来的开销。

watch相关接口:

type Watcher interface {
// Watch watches on a key or prefix. The watched events will be returned
// through the returned channel. If revisions waiting to be sent over the
// watch are compacted, then the watch will be canceled by the server, the
// client will post a compacted error watch response, and the channel will close.
// If the context "ctx" is canceled or timed out, returned "WatchChan" is closed,
// and "WatchResponse" from this closed channel has zero events and nil "Err()".
// The context "ctx" MUST be canceled, as soon as watcher is no longer being used,
// to release the associated resources.
//
// If the context is "context.Background/TODO", returned "WatchChan" will
// not be closed and block until event is triggered, except when server
// returns a non-recoverable error (e.g. ErrCompacted).
// For example, when context passed with "WithRequireLeader" and the
// connected server has no leader (e.g. due to network partition),
// error "etcdserver: no leader" (ErrNoLeader) will be returned,
// and then "WatchChan" is closed with non-nil "Err()".
// In order to prevent a watch stream being stuck in a partitioned node,
// make sure to wrap context with "WithRequireLeader".
//
// Otherwise, as long as the context has not been canceled or timed out,
// watch will retry on other recoverable errors forever until reconnected.
//
// TODO: explicitly set context error in the last "WatchResponse" message and close channel?
// Currently, client contexts are overwritten with "valCtx" that never closes.
// TODO(v3.4): configure watch retry policy, limit maximum retry number
// (see https://github.com/etcd-io/etcd/issues/8980)
Watch(ctx context.Context, key string, opts ...OpOption) WatchChan // RequestProgress requests a progress notify response be sent in all watch channels.
RequestProgress(ctx context.Context) error // Close closes the watcher and cancels all watch requests.
Close() error
}

etcd 的Watch 机制可以实时地订阅到 etcd 中增量的数据更新,watch 支持指定单个 key,也可以指定一个 key 的前缀。Watch 观察将要发生或者已经发生的事件,输入和输出都是流;输入流用于创建和取消观察,输出流发送事件。一个观察 RPC 可以在一次性在多个key范围上观察,并为多个观察流化事件,整个事件历史可以从最后压缩修订版本开始观察。

ETCD数据版本机制

etcd数据版本中主要有term表示leader的任期,revision 代表的是全局数据的版本。当集群发生 Leader 切换,term 的值就会 +1,在节点故障,或者 Leader 节点网络出现问题,再或者是将整个集群停止后再次拉起,都会发生 Leader 的切换;当数据发生变更,包括创建、修改、删除,其 revision 对应的都会 +1,在集群中跨 Leader 任期之间,revision 都会保持全局单调递增,集群中任意一次的修改都对应着一个唯一的 revision,因此我们可以通过 revision 来支持数据的 MVCC,也可以支持数据的 Watch。

对于每一个 KeyValue 数据节点,etcd 中都记录了三个版本:

  • 第一个版本叫做 create_revision,是 KeyValue 在创建时对应的 revision;
  • 第二个叫做 mod_revision,是其数据被操作的时候对应的 revision;
  • 第三个 version 就是一个计数器,代表了 KeyValue 被修改了多少次。

在同一个 Leader 任期之内,所有的修改操作,其对应的 term 值始终相等,而 revision 则保持单调递增。当重启集群之后,所有的修改操作对应的 term 值都加1了。

ETCD之MVCC并发控制

说起mvcc大家都不陌生,mysql的innodb中就使用mvcc实现高并发的数据访问,对数据进行多版本处理,并通过事务的可见性来保证事务能看到自己应该看到的数据版本,同样,在etcd中也使用mvcc进行并发控制。

etcd支持对同一个 Key 发起多次数据修改,每次数据修改都对应一个版本号。etcd记录了每一次修改对应的数据,即一个 key 在 etcd 中存在多个历史版本。在查询数据的时候如果不指定版本号,etcd 会返回 Key 对应的最新版本,同时etcd 也支持指定一个版本号来查询历史数据。

etcd将每一次修改都记录下来,使用 watch订阅数据时,可以支持从任意历史时刻(指定 revision)开始创建一个 watcher,在客户端与 etcd 之间建立一个数据管道,etcd 会推送从指定 revision 开始的所有数据变更。etcd 提供的 watch 机制保证,该 Key 的数据后续的被修改之后,通过这个数据管道即时的推送给客户端。

分析其源码可知:

type revision struct {
// main is the main revision of a set of changes that happen atomically.
main int64 // sub is the the sub revision of a change in a set of changes that happen
// atomically. Each change has different increasing sub revision in that
// set.
sub int64
} func (a revision) GreaterThan(b revision) bool {
if a.main > b.main {
return true
}
if a.main < b.main {
return false
}
return a.sub > b.sub
}

在etcd的mvcc实现中有一个revision结构体,main 表示当前操作的事务 id,全局自增的逻辑时间戳,sub 表示当前操作在事务内部的子 id,事务内自增,从 0 开始;通过GreaterThan方法进行事务版本的比较。

ETCD存储数据结构

etcd 中所有的数据都存储在一个 btree的数据结构中,该btree保存在磁盘中,并通过mmap的方式映射到内存用来支持快速的访问,treeIndex的定义如下:

type treeIndex struct {
sync.RWMutex
tree *btree.BTree
} func newTreeIndex() index {
return &treeIndex{
tree: btree.New(32),
}
}

index所绑定对btree的操作有Put、Get、Revision、Range及Visit等,以Put方法为例,其源码如下:

func (ti *treeIndex) Put(key []byte, rev revision) {
keyi := &keyIndex{key: key} ti.Lock()
defer ti.Unlock()
item := ti.tree.Get(keyi)
if item == nil {
keyi.put(rev.main, rev.sub)
ti.tree.ReplaceOrInsert(keyi)
return
}
okeyi := item.(*keyIndex)
okeyi.put(rev.main, rev.sub)
}

通过源码可知对btree数据的读写操作都是在加锁下完成的,从而来保证并发下数据的一致性。

ETCD核心机制解析的更多相关文章

  1. 《高性能SQL调优精要与案例解析》一书谈主流关系库SQL调优(SQL TUNING或SQL优化)核心机制之——索引(index)

    继<高性能SQL调优精要与案例解析>一书谈SQL调优(SQL TUNING或SQL优化),我们今天就谈谈各主流关系库中,占据SQL调优技术和工作半壁江山的.最重要的核心机制之一——索引(i ...

  2. Asp.Net WebApi核心对象解析(下篇)

    在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...

  3. Qt核心机制与原理

    转:  https://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★ ...

  4. Asp.Net WebApi核心对象解析(二)

    在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...

  5. javashop技术培训总结,架构介绍,Eop核心机制

    javashop技术培训一.架构介绍1.Eop核心机制,基于spring的模板引擎.组件机制.上下文管理.数据库操作模板引擎负责站点页面的解析与展示组件机制使得可以在不改变核心代码的情况下实现对应用核 ...

  6. 7 -- Spring的基本用法 -- 3... Spring 的核心机制 : 依赖注入

    7.3 Spring 的核心机制 : 依赖注入 Spring 框架的核心功能有两个. Spring容器作为超级大工厂,负责创建.管理所有的Java对象,这些Java对象被称为Bean. Spring容 ...

  7. Asp.Net WebApi核心对象解析

    在接着写Asp.Net WebApi核心对象解析(下篇)之前,还是一如既往的扯扯淡,元旦刚过,整个人还是处于晕的状态,一大早就来处理系统BUG,简直是坑爹(好在没让我元旦赶过来该BUG),队友挖的坑, ...

  8. Qt核心机制和原理

    转:http://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★了解Q ...

  9. CDN 的缓存与回源机制解析

    CDN的缓存与回源机制解析 CDN (Content Delivery Network,即内容分发网络)指的是一组分布在各个地区的服务器.这些服务器存储着数据的副本,因此服务器可以根据哪些服务器与用户 ...

随机推荐

  1. sqli-labs第二关 详解

    学会了第一关,时隔多天来到了第二关,怎么说了没有看wp就给做出来了. 首先,我是自己先判断了下,这个是什么注入 ?id=1' 不行 ?id=1' or '1'='1--+ 也不行 然后又尝试了下 ?i ...

  2. pytorch和tensorflow的爱恨情仇之参数初始化

    pytorch和tensorflow的爱恨情仇之基本数据类型 pytorch和tensorflow的爱恨情仇之张量 pytorch和tensorflow的爱恨情仇之定义可训练的参数 pytorch版本 ...

  3. 092 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 02 static关键字(中)

    092 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  4. MQTT消息队列压力测试

    环境准备: jmeter插件下载:mqttxmeter1.0.1jarwithdependencies.jar 把MQTT插件放在 %JMeter_Home%/lib/ext下.重启jmeter. M ...

  5. 达梦产品技术支持培训-day7-DM8数据库备份与还原-原理

    (本文部分内容摘自DM产品技术支持培训文档,如需要更详细的文档,请查询官方操作手册,谢谢) 1.DM8备份还原简介 1.1.基本概念 (1)表空间与数据文件 ▷ DM8表空间类型: ▷ SYSTEM ...

  6. Prometheus 系列开篇:为什么要学 Prometheus ?

    「Prometheus 系列开篇:为什么要学 Prometheus ?」首发于[陈树义]公众号,点击跳转到原文https://mp.weixin.qq.com/s/HCS6X3l6nVBw_hAnd6 ...

  7. nginx的变量系统

    本来想写一下nginx的脚本引擎的,但是看起来实在是有点庞大,一时间还不知道该从哪里写比较好.就先写一下他的变量系统吧,这是脚本引擎非常重要的组成部分. 首先为了表述清楚先规定几个术语吧 内置变量:n ...

  8. 多测师讲解 自动化测试理论(1)_高级讲师肖sir

    自动化测试理论什么是自动化测试?广义的:通过工具或程序替代或辅助人工测试的行为叫自动化测试狭义的:通过工具录制或编写脚本模拟手工测试的过程,通过回放或运行脚本执行测试用例,从而代替人工对系统的功能验证 ...

  9. 多测师讲解_肖sir _rf报错归纳(1):

    错误一: 报错原因:文件格式 解决方案: 修改文件格式,将txt改成robot格式   错误二: rf 运行以后出现乱码现象 解决方案: 打开python的安装路径下:C:\python37\Lib\ ...

  10. 【idea】重装系统(格式化C盘后)idea无法导入maven(jdk重装了,版本不同),结果报错!

    [以下部分截图]2019-11-25 09:48:49,045 [ 108964]   WARN -      #org.jetbrains.idea.maven - Cannot open inde ...