简介

Etcd是CoreOS基于Raft协议开发的分布式key-value存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。

在分布式系统中,如何管理节点间的状态一直是一个难题,etcd像是专门为集群环境的服务发现和注册而涉及,它提供了数据TTL失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便的跟踪并管理集群节点的状态。

特点

  • 简单:curl可访问的用户的API(HTTP + JSON)
  • 安全:可选的SSL客户端证书认证
  • 快速: 单实例每秒1000次写操作
  • 可靠:使用Raft算法保证一致性

主要功能

  • 基本的key-value存储
  • 监听机制
  • key的过期及续约机制, 用于监控和服务发现
  • 原子Compare And Swap和Compare And Delete, 用于分布式锁和leader选举

写数据流程

image-1667810552220

  • 当客户端对etcd发起请求的时候,如果etcd不是leader的状态而是follower,follower则会将请求转发leader; 如果是leader后, 会对其进行预检查,检查(配额、限速、鉴权【判断请求是否合法】、包大小【需要小于1.5M,过大则会拒绝】)。
  • 如果请求本身是合法的,会将请求转发给KVServer处理。
  • KVserver一致性模块进行数据处理,一致性模块是基于raft协议实现的,这时候的数据本身是处于unstable状态。
  • 当leader该数据处理unstable状态后,会通过rpc通知其他follower也来同步该数据,并且leader本身会在数据同步到日志模块【wal日志, wal日志通过fsync落盘到磁盘中】。而其他follow在同步该数据的时候,本身完成的是步骤3和数据同步到日志模块,follower一致性模块数据变成commited状态,当完成了这些后通过上次rpc返回响应体给leader。
  • leader在收到了超过半数集群本身确认后,更新MatchIndex, 一致性模块中数据本身由unstable变化成commited状态。这时候通过MVCC模块【treeIndex和BoltDB开源组件组成】进行状态机的写入,将数据同步到treeIndex【会更新modified版本[当前版本号], generations信息[创建的版本,当前版本数,过往的所有版本号]】。再通过BoltDB落盘到磁盘中。这时候一致性模块数据由commited变化为applied状态。【在这里如果没有要求数据强一致性,弱一致性的话,那么数据在commited状态就认为数据已经同步完成了】。
  • 再通过heatbeat将数据同步到follower中MVCC模块中。最终完成数据的一致性。如下图所示。 【如果follower比leader落后好几个版本,leader会通过headbeat带到follower进行同步】。

架构流程

image-1667810261976
从上图可知,etcd 有 etcd Server、gRPC Server、存储相关的 MVCC 、Snapshot、WAL,以及 Raft 模块。
其中:

  • etcd Server 用于对外接收和处理客户端的请求;
  • gRPC Server 则是 etcd 与其他 etcd 节点之间的通信和信息同步;
  • MVCC 即多版本控制,etcd 的存储模块,键值对的每一次操作行为都会被记录存储,这些数据底层存储在 BoltDB 数据库中;
  • WAL 预写式日志,etcd 中的数据提交前都会记录到日志;
  • Snapshot 快照,以防 WAL 日志过多,用于存储某一时刻 etcd 的所有数据;
  • Snapshot 和 WAL 相结合,etcd 可以有效地进行数据存储和节点故障恢复等操作;

虽然 etcd 内部实现机制复杂,但对外提供了简单的 API 接口,方便客户端调用。我们可以通过 etcdctl 客户端命令行操作和访问 etcd 中的数据,或者通过HTTP API 接口直接访问 etcd。
etcd 中的数据结构很简单,它的数据存储其实就是键值对的有序映射。etcd 还提供了一种键值对监测机制,即 Watch 机制,客户端通过订阅相关的键值对,获取其更改的事件信息。Watch 机制实时获取 etcd 中的增量数据更新,使数据与 etcd 同步。
etcd 目前有 V2.x 和 V3.x 两个大版本。etcd V2 和 V3 是在底层使用同一套 Raft 算法的两个独立应用,但相互之间实现原理和使用方法上差别很大,接口不一样、存储不一样,两个版本的数据互相隔离。
至于由 etcd V2 升级到 etcd V3 的情况,原有数据只能通过 etcd V2 接口访问,V3 接口创建的数据只能通过新的 V3 的接口访问。
架构图:
image-1667810180345
==通常,etcd 会监听两个端口,默认是 2379 端口和 2380 端口。其中,2380 端口用于集群内部通信,主要涉及集群间数据同步、心跳、选举等。2379 端口用于与客户端通信,比如接收客户端发起的读/写数据请求。==
etcd 节点在部署的时候有两种运行模式:集群模式和代理模式。
当 etcd 节点以集群模式运行时,它会加入已有集群中,作为集群的一部分。也就是说,后续的心跳、数据同步、选举等它都会参与。
集群模式中的节点数一般采用奇数个。为什么呢?因为假如同时有两个 Candidate 发起选举,如果是偶数节点的话,可能存在两个 Candidate 获得相同票数。
这会导致什么问题?如果两个 Candidate 票数一样,就需要再次发起选举,而再次发起选举还是有一定概率出现票数一样,这会导致选举耗时较多,影响稳定性。所以,采用奇数个节点,能有效降低票数一样的概率,提升选举的效率。另外,使用奇数节点来部署,也能让 etcd 很好地处理分区容错问题。
当某个 etcd 节点以代理模式运行时,该节点负责将接收到的请求转发给 etcd 集群节点。目前 etcd 接口有 v2 和 v3 两个版本,其中 v2 是 HTTP 接口,v3 是 gRPC 接口。需要注意的是,代理模式只支持转发 v2 版本的请求,也就是只支持转发 HTTP 请求。
image-1667810230387
不过,由于 etcd v3 接口在性能、安全、稳定性等方面要比 v2 接口优秀很多,新项目倾向于使用 v3 接口,老项目也逐渐从 v2 接口迁移到 v3 接口。也就是说,代理模式以后可能逐渐被淘汰掉。

单点部署

ETCD_VERSION='3.5.4'
wget https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz
#如果采用离线安装的方式,那么需要将下载的包拷贝
到目标主机上进行解压安装
tar -zxvf etcd-v3.5.4-linux-amd64.tar.gz --strip-components=1 -C /usr/local/bin etcd-v3.5.4-linux-amd64/etcd{,ctl}

# start etcd server,这个只能在自己的设备上登录,其他设备无法进行连接
nohup etcd &

# 使用这个命令启动,外网可以访问登录
etcd --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379'

#创建service
vim /usr/lib/systemd/system/etcd.service
[Unit]
Description=EtcdService
Documentation=https://coreos.com/etcd/docs/latest/
After=network.target

[Service]
Type=notify
ExecStart=/usr/local/bin/etcd
Restart=on-failure
RestartSec=10
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
Alias=etcd3.service

systemctl daemon-reload && systemctl enable --now etcd

数据操作

etcdctl

  • put(key,value)/delete(key),创建/修改、删除
  • get(key)/get(keyfrom,keyend),获取
  • watch(key/keyprefix),对key进行监视,可第一时间知道修改的内容
  • transactions(if/then/else ops).commit(),判断并根据判断的结果执行不同的操作
  • leases: grant / revoke / keepalive,租约管理
# 插入数据
[root@node2 ~]# etcdctl put key1 value1		
OK

# 查看数据
[root@node2 ~]# etcdctl get key1				
key1
value1

# 批量插入数据
[root@node2 ~]# cat createdata 
etcdctl put key0 value0
etcdctl put key1 value1
.....
etcdctl put key8 value8
etcdctl put key9 value9
[root@node2 ~]# cat createdata | sh -x 				
+ etcdctl put key0 value0
OK
+ etcdctl put key1 value1
OK
......
+ etcdctl put key8 value8
OK
+ etcdctl put key9 value9
OK
[root@node2 ~]# etcdctl get key5
key5
value5

# 查询一个区间key的数据,左开右闭,不包含结束key
[root@node2 ~]# etcdctl get key2 key6		
key2
value2
key3
value3
key4
value4
key5
value5

# 指定key的前缀进行查询,列出所有key*的数据
[root@node2 ~]# etcdctl get key --prefix		
key0
value0
key1
value1
........
key8
value8
key9
value9

# 删除一个key-value
[root@node2 ~]# etcdctl del key9

数据信息详解

# 以json方式详细查看一个key的信息
[root@node2 ~]# etcdctl get key0 -w json | jq
{
  "header": {
    "cluster_id": 14841639068965180000,
    "member_id": 10276657743932975000,
    "revision": 12,
    "raft_term": 2
  },
  "kvs": [
    {
      "key": "a2V5MA==",
      "create_revision": 3,
      "mod_revision": 3,
      "version": 1,
      "value": "dmFsdWUw"
    }
  ],
  "count": 1
}

对上述输出进行分析:

  • count:返回数据的数量
  • header:消息头,描述在etcd集群中全局的信息
  • cluster_id:集群id
  • member_id:
  • revision:全局版本号,每更改一次数据,此数据会单项递增1。如此时执行etcdctl put key100 value100。再次查看可方法revision变成13。
  • raft_term:leader每变更一次,此数据会单项递增1。
  • kvs:键值信息
  • key:键,base64
  • create_revision:创建version
  • mod_revision:修改version,如果对key0进行修改,会与revision值保持一致
  • version:版本号,如果对key0进行修改,那么此值+1
  • value:值,base64

参考链接

https://blog.csdn.net/wohu1104/article/details/115764681
https://blog.csdn.net/weixin_39540280/article/details/122007678

星霜荏苒 居诸不息