前言
在分布式领域有一项金科玉律就是CAP理论,分别是一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)。
程序猿都比较懒,大家直接使用这三个单词首字母简化成CAP理论的缩写。
一致性表示客户端访问分布式集群下的不同节点,他们的返回值一定是幂等的。
可用性表示分布式集群下节点可服务能力, 即在高并发压力下能否对请求进行处理。
分区容错性表示集群下部分节点网络异常也能保证客户端的请求能正常处理。
一个分布式系统是不能同时满足CAP三大要素,比如你要了一致性你的分区容错性就无法满足。
什么是ETCD
前言里的CAP理论是ETCD提供服务的基石,ETCD是一款分布式的Key/Value数据库,是CoreOS团队在2013发起的一款开源项目,开发语言为go,目前已经是github上的明星开源项目,且大名鼎鼎的K8S都使用ETCD存储其核心数据。
ETCD官网地址:https://etcd.io/
ETCD性能
这是官网提供的性能指标:
https://etcd.io/docs/v3.4.0/op-guide/performance/
注意官网是采取固态硬盘进行测试的~
这是博客园的园友的测试指标:
https://www.cnblogs.com/sunsky303/p/11127487.html
为方便对比,报告中加入了Zookeeper、Consul。
ETCD特点
强一致性
性能强悍(单实例1秒支持1000次写入)
支持分布式集群化部署
提供gRPC与服务端通讯
提供RESTfull代理(接收http请求后转gRPC)
支持SSL/TLS安全协议
提供多种身份认证方式(如证书认证、basac认证)
键值对存储
Key可以设置TTL(生命周期)
Key可通过租约方式进行续约
Key支持前缀搜索
原则性操作,轻松实现分布式锁
可通过Watch方式轻松实现发布订阅
内部使用Raft算法保证节点强一致性
权限控制
Raft算法
ETCD通过Raft算法要求所有请求必须统一由Leader作为数据的入口。
在Raft算法中,Leader节点采取轮询方式与各Follower节点发送心跳包进行健康检测,同时会对比各自的请求日志,所以Leader节点一定会拥有etcd所有的操作日志。
Raft算法以选举方式生成Leader节点,Leader异常后(Follower在超出预设的时间段内没有接收到Leader的心跳包)Follower节点可变更角色为Candidate进行Leader选举,获得大多数Follower投票后即可升级为Leader 。
想要了解Raft算法的详细信息可查看Raft的论文:
https://docs.qq.com/doc/DY0VxSkVGWHFYSlZJ
ETCD用途
服务注册与发现
分布式配置
分布式锁
分布式回调
服务发现
在ETCD注册以服务名+服务节点URL为Key的的键值后通过GetRange这个API可获取同一前缀的所有键值对信息,通过WatchRange这个API可对同一前缀的键值对进行监控。
通过这种方式轻松实现服务注册与发现,且应用程序对ETCD服务节点的变更可及时响应。
下面是服务注册与发现的流程图

服务注册的Key名称可以如下格式:
/services/order-api-main/192.168.1.1:5101/services/order-api-main/192.168.1.2:5101/services/order-api-main/192.168.1.3:5101
通过前缀 services/order-api-main 可以Get到上面3个服务实例。
分布式配置中心
在微服务架构下我们需要将配置信息统一存储,且需要支持更新配置后能及时推送给引用了配置中心的应用服务端,ETCD的Watch机制完美的解决了这一点。
分布式锁
当我们以集群的方式对外提供服务则可能存在资源竞争关系,单体服务直接通过编程语言自带的锁机制即可,但在集群方式下则必须引入第三方组件来实现分布式锁功能。
设计分布式锁需要考虑以下几点
1、锁需要有自动解锁机制(防止加锁应用挂掉后导致锁无法释放问题);
2、需要保证互斥性,保证加锁后只有一个服务节点进程内对锁资源的使用;
3、需要有解锁机制,且正常情况下哪个服务节点加锁也由它解锁;
ETCD的Key在push时允许加入TTL(生命周期)参数,在不进行续约的前提下可自动删除Key,从而满足自动解锁条件。
每一个Key从创建到删除都会携带几个记录当前Key关键信息的属性(在讲解etcd-manager小节里会详细说明),其中Version字段是一个数值类型的属性,它可以记录Key的操作行为,每一次Put则该值+1,删除则变更为0。是的,哪怕你删除了这个Key在ETCD里只要重新写入相同的Key则还是可以查阅得到该Key的Version信息,通过它可以知晓这个名称的Key的所有变更信息,且Version的变更是原子性行为,通过Key的Version可以实现分布式锁的原子性行为。
ETCD单例安装
这里假定服务器环境为Linux。
使用yum下载并安装etcd
yum install etcd
配置etcd文件,etcd.conf文件的ETCD_LISTEN_CLIENT_URLS配置项表示客户端监听IP,设置为http://0.0.0.0:2379表示允许任何客户端连接。
vi etc/etcd/etcd.conf# [member]ETCD_NAME=default #etcd服务名字ETCD_DATA_DIR="/data/etcd/default.etcd" #etcd数据目录#ETCD_WAL_DIR=""#ETCD_SNAPSHOT_COUNT="10000"#ETCD_HEARTBEAT_INTERVAL="100"#ETCD_ELECTION_TIMEOUT="1000"ETCD_LISTEN_PEER_URLS="http://lcalhost:2380"ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379" #客户端访问的地址和端口#ETCD_MAX_SNAPSHOTS="5"#ETCD_MAX_WALS="5"#ETCD_CORS=""
启动/查看etcd,因使用yum方式下载的etcd会自动给我们进行服务配置,所以可以直接使用命令systemctl对etcd进行启动、停止、以及设置开机自启动。
systemctl restart etcdsystemctl status etcd
停止etcd服务,设置开机自启动
systemctl stop etcdsystemctl enable etcd
使用yum安装的etcd如需后续需要更改etcd配置则在/etc/etcd/目录下操作etcd.conf即可。
安装好之后就可以使用etcdctl命令对etcd进行操作了。
如,查看版本
etcdctl version

ETCD集群
etcd作为一个分布式K/V数据库自然是支持集群方式部署,同一集群下推荐使用单数节点。官方推荐使用3、5、7、9等非双数ETCD实例组成集群,这也是Raft算法所推崇的。
etcd支持三种集群启动方式
静态启动
动态发现启动
DNS发现启动
本文讲解下静态启动和动态发现启动,便于后面理解,这里使用3个节点做etcd集群测试,集群各节点信息如下:

etcd集群静态启动
通过/etc/etcd/etcd.conf配置文件的[Clustering]
节点下配置集群信息。
静态启动方式通常在使用者已知晓集群所有节点信息的前提下使用,因此方式需要在[Clustering]
下面配置好节点通讯信息以及集群的基础信息。静态启动集群所依赖的关键属性含义参考下面表格:
| 属性名称 | 含义 |
| ETCD_INITIAL_ADVERTISE_PEER_URLS | 集群下其他节点通过此属性值与当前节点通讯(同步日志、选举投票),静态启动方式下此属性必须配置。与[Membe]节点下的ETCD_LISTEN_PEER_URLS节点一致即可 |
| ETCD_ADVERTISE_CLIENT_URLS | 用于通知其他ETCD节点,客户端接入本节点的监听地址。与[Membe]节点下的ETCD_LISTEN_CLIENT_URLS一致即可。 |
| ETCD_INITIAL_CLUSTER | 描述当前集群下所有节点的通讯地址信息, ETCD_INITIAL_CLUSTER参数中配置的url地址必须与各个节点启动时设置的ETCD_INITIAL_ADVERTISE_PEER_URLS参数相同 |
| ETCD_INITIAL_CLUSTER_TOKEN | etcd集群唯一标识 |
| ETCD_INITIAL_CLUSTER_STATE | 标识该节点在集群中是否已注册,枚举值:new \existing new=首次在当前集群中注册,一般在扩展集群时或初始化集群时候设为new,已注册到当前集群后设置为existing 如若设置错误则启动etcd会有节点已存在的异常信息。 |
更多etcd配置信息可参考官网提供的说明:
https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/configuration.md
下面是节点etcd02的配置文件(/etc/etcd/etcd.conf)信息,注意集群模式需要更改ETCD_NAME属性,同一集群不同节点需要有不同的ETCD_NAME。
#[Member]#ETCD_CORS=""ETCD_DATA_DIR="/var/lib/etcd/default.etcd"#ETCD_WAL_DIR=""ETCD_LISTEN_PEER_URLS="http://192.168.44.102:2380"ETCD_LISTEN_CLIENT_URLS="http://192.168.44.102:2379,http://127.0.0.1:2379"#ETCD_MAX_SNAPSHOTS="5"#ETCD_MAX_WALS="5"ETCD_NAME="etcd02"#ETCD_SNAPSHOT_COUNT="100000"#ETCD_HEARTBEAT_INTERVAL="100"#ETCD_ELECTION_TIMEOUT="1000"#ETCD_QUOTA_BACKEND_BYTES="0"#ETCD_MAX_REQUEST_BYTES="1572864"#ETCD_GRPC_KEEPALIVE_MIN_TIME="5s"#ETCD_GRPC_KEEPALIVE_INTERVAL="2h0m0s"#ETCD_GRPC_KEEPALIVE_TIMEOUT="20s"#[Clustering]ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.44.102:2380"ETCD_ADVERTISE_CLIENT_URLS="http://192.168.44.102:2379,http://127.0.0.1:2379"ETCD_INITIAL_CLUSTER="etcd01=http://192.168.44.101:2380,etcd02=http://192.168.44.102:2380,etcd03=http://192.168.44.103:2380"ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-test"ETCD_INITIAL_CLUSTER_STATE="existing"#ETCD_DISCOVERY=""#ETCD_DISCOVERY_FALLBACK="proxy"#ETCD_DISCOVERY_PROXY=""#ETCD_DISCOVERY_SRV=""#ETCD_STRICT_RECONFIG_CHECK="true"#ETCD_ENABLE_V2="true"##[Proxy]#ETCD_PROXY="off"#ETCD_PROXY_FAILURE_WAIT="5000"#ETCD_PROXY_REFRESH_INTERVAL="30000"#ETCD_PROXY_DIAL_TIMEOUT="1000"#ETCD_PROXY_WRITE_TIMEOUT="5000"#ETCD_PROXY_READ_TIMEOUT="0"##[Security]#ETCD_CERT_FILE=""#ETCD_KEY_FILE=""#ETCD_CLIENT_CERT_AUTH="false"#ETCD_TRUSTED_CA_FILE=""#ETCD_AUTO_TLS="false"#ETCD_PEER_CERT_FILE=""#ETCD_PEER_KEY_FILE=""#ETCD_PEER_CLIENT_CERT_AUTH="false"#ETCD_PEER_TRUSTED_CA_FILE=""#ETCD_PEER_AUTO_TLS="false"##[Logging]#ETCD_DEBUG="false"#ETCD_LOG_PACKAGE_LEVELS=""#ETCD_LOG_OUTPUT="default"##[Unsafe]#ETCD_FORCE_NEW_CLUSTER="false"##[Version]#ETCD_VERSION="false"#ETCD_AUTO_COMPACTION_RETENTION="0"##[Profiling]#ETCD_ENABLE_PPROF="false"#ETCD_METRICS="basic"##[Auth]#ETCD_AUTH_TOKEN="simple"
其他两个etcd节点的配置文件就不贴了,把上面说的关键属性配置好即可。
测试集群是否配置成功
随便在哪个etcd节点下的Linux会话中输入下面脚本查看集群列表。
etcdctl member list
结果如下

尝试在etcd01节点下写入一个key,如下:
etcdctl put zp 帅气小鹏哥是也

在etcd02和etcd03获取刚写入的zp这个key:
etcdctl get zp
etcd02下获取到结果,如下:

etcd03下获取到结果,如下:

至此就已经通过静态启动方式将etcd集群搭建并正常启动和运行了。
etcd集群动态发现启动
还有一些情况就是集群内节点的IP是提前无法预知的,这在使用云提供商或网络使用 DHCP 时很常见。在这些情况下,就不能使用静态启动集群的模式了,可以使用动态发现方式启动集群。
动态发现方式需要依赖外部的一个服务注册中心,这里使用etcd官网提供的服务注册中心https://discovery.etcd.io。
流程如下:
1、向服务注册中心注册服务,指定服务对应的etcd节点数;
2、启动etcd时给参数ETCD_DISCOVERY配置从服务注册中心返回的一个Token值;
这里还是在之前那三台虚拟机上做测试,测试前停止各节点的etcd服务
systemctl stop etcd
向https://discovery.etcd.io注册,指定节点数为3
curl https://discovery.etcd.io/new?size=3https://discovery.etcd.io/73ce53544e2f60997a8555287b6f3658[root@D02 ~]#
将之前etcd各节点下的数据库节点信息(member)删除,删除前先备份下member
cd /var/lib/etcd/default.etcdcp -r member memberBakrm -rf member
更新配置文件,修改etcd名称与集群相关配置。还是和上面一样,使用etcd02节点测试。
vim /etc/etcd/etcd.conf
更新配置文件后的值如下:
#[Member]#ETCD_CORS=""ETCD_DATA_DIR="/var/lib/etcd/default.etcd"#ETCD_WAL_DIR=""ETCD_LISTEN_PEER_URLS="http://192.168.44.102:2380"ETCD_LISTEN_CLIENT_URLS="http://192.168.44.102:2379,http://127.0.0.1:2379"#ETCD_MAX_SNAPSHOTS="5"#ETCD_MAX_WALS="5"ETCD_NAME="etcd2"#ETCD_SNAPSHOT_COUNT="100000"#ETCD_HEARTBEAT_INTERVAL="100"#ETCD_ELECTION_TIMEOUT="1000"#ETCD_QUOTA_BACKEND_BYTES="0"#ETCD_MAX_REQUEST_BYTES="1572864"#ETCD_GRPC_KEEPALIVE_MIN_TIME="5s"#ETCD_GRPC_KEEPALIVE_INTERVAL="2h0m0s"#ETCD_GRPC_KEEPALIVE_TIMEOUT="20s"##[Clustering]ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.44.102:2380"ETCD_ADVERTISE_CLIENT_URLS="http://192.168.44.102:2379,http://127.0.0.1:2379"#ETCD_INITIAL_CLUSTER="etcd01=http://192.168.44.101:2380,etcd02=http://192.168.44.102:2380,etcd03=http://192.168.44.103:2380"ETCD_DISCOVERY="https://discovery.etcd.io/73ce53544e2f60997a8555287b6f3658"#ETCD_DISCOVERY_FALLBACK="proxy"#ETCD_DISCOVERY_PROXY=""#ETCD_DISCOVERY_SRV=""#ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-1"#ETCD_INITIAL_CLUSTER_STATE="new"#ETCD_STRICT_RECONFIG_CHECK="true"#ETCD_ENABLE_V2="true"##[Proxy]#ETCD_PROXY="off"#ETCD_PROXY_FAILURE_WAIT="5000"#ETCD_PROXY_REFRESH_INTERVAL="30000"#ETCD_PROXY_DIAL_TIMEOUT="1000"#ETCD_PROXY_WRITE_TIMEOUT="5000"#ETCD_PROXY_READ_TIMEOUT="0"##[Security]#ETCD_CERT_FILE=""#ETCD_KEY_FILE=""#ETCD_CLIENT_CERT_AUTH="false"#ETCD_TRUSTED_CA_FILE=""#ETCD_AUTO_TLS="false"#ETCD_PEER_CERT_FILE=""#ETCD_PEER_KEY_FILE=""#ETCD_PEER_CLIENT_CERT_AUTH="false"#ETCD_PEER_TRUSTED_CA_FILE=""#ETCD_PEER_AUTO_TLS="false"##[Logging]#ETCD_DEBUG="false"#ETCD_LOG_PACKAGE_LEVELS=""
如上配置文件所述,我更新了的属性有ETCD_NAME,
注释了ETCD_INITIAL_CLUSTER、ETCD_INITIAL_CLUSTER_TOKEN、
ETCD_INITIAL_CLUSTER_STATE
新增了的属性有ETCD_DISCOVERY="https://discovery.etcd.io/73ce53544e2f60997a8555287b6f3658"
将其他节点依次更新,还是一样名称不能重复,ETCD_DISCOVERY的值都设置成一致的即可。然后开启etcd服务。
配置好后依次校验各个节点,输入脚本:
etcdctl member list

可以看到,节点集合中的名称从之前的etcd01变为etcd1,etcd02变为etcd2,etcd03变为etcd3,说明当前的etcd集群和之前的不再是同一个集群。
接下来测试在etcd1节点写入key
etcdctl put zp 大鹏展翅

再到etcd2和etcd3节点尝试获取key。
etcdctl get zp

至此,etcd的两种启动集群方式已经全部实现了一遍。今天的实操就到这,DNS方式就不写了,哈哈哈~。
总结
etcd可存储不经常变动但是各分布式应用服务必不可少的数据,比如服务注册与发现。同时需要注意到etcd会记录key的所有生命周期(创建到删除),且同一个key删除后再创建依旧会保留它之前的信息,这就是它的多版本特性。还有许多牛逼的功能我自己也还没尝试,后续有机会再一一完善~




