ETCD集群部署
ETCD安装,请参考文章
《ETCD系列教程2 - ETCD体验》 吕梦楼,公众号:楼仔进阶之路ETCD教程-3.ETCD体验
下面我们可以部署一个etcd集群,我把代码还是写到文件中,第一个脚本为不支持在 Docs 外粘贴 block,内容如下(启动etcd需要很多参数,这些参数我都已经注释说明,更多参数详见:https://www.cnblogs.com/linuxws/p/11194403.html):
TOKEN=token-01 CLUSTER_STATE=new NAME_1=etcd-01 NAME_2=etcd-02 NAME_3=etcd-03 HOST_1=127.0.0.1 HOST_2=127.0.0.1 HOST_3=127.0.0.1 PORT_API_1=2379 PORT_PEER_1=2380 PORT_API_2=2479 PORT_PEER_2=2480 PORT_API_3=2579 PORT_PEER_3=2580 CLUSTER=${NAME_1}=http://${HOST_1}:${PORT_PEER_1},${NAME_2}=http://${HOST_2}:${PORT_PEER_2},${NAME_3}=http://${HOST_3}:${PORT_PEER_3} # For every machine THIS_NAME=${NAME_1} THIS_IP=${HOST_1} THIS_PORT_API=${PORT_API_1} THIS_PORT_PEER=${PORT_PEER_1} # 用于杀死进程 lsof -i:2379 | awk '{print $2}' | grep -v "PID" | uniq | xargs kill -9 # --enable-v2 支持v2接口,可以省略 # --data-dir 数据存储目录,可以省略 # --name 节点名称,必须 # --initial-advertise-peer-urls 数据在集群内进行交互的url,必须 # --listen-peer-urls 集群节点之间通信监听的url,必须 # --advertise-client-urls 客户通过该地址与本member交互信息,可以省略 # --listen-client-urls 监听客户端请求的url,必须 # --initial-cluster 初始启动的集群配置,必须 # --initial-cluster-state 初始化集群状态,取值为new和existing,可以省略 # --initial-cluster-token 集群初始化token,可以省略 etcd --enable-v2=true --data-dir=data.${THIS_NAME} --name ${THIS_NAME} \ --initial-advertise-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} --listen-peer-urls http://${THIS_IP}:${THIS_PORT_PEER} \ --advertise-client-urls http://${THIS_IP}:${THIS_PORT_API} --listen-client-urls http://${THIS_IP}:${THIS_PORT_API} \ --initial-cluster ${CLUSTER} \ --initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
第二个脚本需要把里面的内容替换如下:
# For every machine THIS_NAME=${NAME_2} THIS_IP=${HOST_2} THIS_PORT_API=${PORT_API_2} THIS_PORT_PEER=${PORT_PEER_2} # 用于杀死进程 lsof -i:2479 | awk '{print $2}' | grep -v "PID" | uniq | xargs kill -9
第三个脚本需要把里面的内容替换如下:
# For every machine THIS_NAME=${NAME_3} THIS_IP=${HOST_3} THIS_PORT_API=${PORT_API_3} THIS_PORT_PEER=${PORT_PEER_3} # 用于杀死进程 lsof -i:2579 | awk '{print $2}' | grep -v "PID" | uniq | xargs kill -9
有了这3个脚本,分别开3个窗口,分别执行,服务启动截图如下:
当这3个脚本全部启动后,集群部署完毕,我们检查一下3个节点的健康状态:
curl http://127.0.0.1:2379/health curl http://127.0.0.1:2479/health curl http://127.0.0.1:2579/health
如果都返回{"health":"true"},表示部署成功,下面我们查看一下部署的节点信息:
curl http://127.0.0.1:2379/v2/members
返回结果如下,其中peerURLs是节点互相通信访问的url,clientURLs是对外访问的url:
{ "members":[ { "id":"264ae6bc59e99892", "name":"etcd-01", "peerURLs":[ "http://127.0.0.1:2380" ], "clientURLs":[ "http://127.0.0.1:2379" ] }, { "id":"dbafe5ad6b652eda", "name":"etcd-02", "peerURLs":[ "http://127.0.0.1:2480" ], "clientURLs":[ "http://127.0.0.1:2479" ] }, { "id":"f570ae41f524bdcb", "name":"etcd-03", "peerURLs":[ "http://127.0.0.1:2580" ], "clientURLs":[ "http://127.0.0.1:2579" ] } ] }
ETCD使用
集群管理
我们在部署集群时,用到一些方法,这里我简单汇总一下:
// 版本检查,输出{"etcdserver":"3.4.14","etcdcluster":"3.4.0"} curl http://127.0.0.1:2379/version // 健康检查,输出{"health":"true"} curl http://127.0.0.1:2379/health // 查看集群节点 curl http://127.0.0.1:2379/v2/members
键值操作
设置键的值:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world"
返回结果:
{ "action":"set", "node":{ "key":"/message", "value":"hello world", "modifiedIndex":43, "createdIndex":43 } }
读取键的值:
curl http://127.0.0.1:2379/v2/keys/message
返回结果:
{ "action":"get", "node":{ "key":"/message", "value":"hello world", "modifiedIndex":43, "createdIndex":43 } }
给键设置10s的超时时间:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world" -d ttl=10
返回结果(prevNode是旧值):
{ "action":"set", "node":{ "key":"/message", "value":"hello world", "expiration":"2021-01-21T00:16:13.777434Z", "ttl":10, "modifiedIndex":44, "createdIndex":44 }, "prevNode":{ "key":"/message", "value":"hello world", "modifiedIndex":43, "createdIndex":43 } }
获取该键值,超时后,就提示“key not found”:
watch通知
可以对key设置监听,当key的值有变化时,会通知监听的客户端,我们先在客户端A监听key:
curl http://127.0.0.1:2379/v2/keys/message?wait=true
然后在客户端B,修改该key的值:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world2"
客户端A返回并退出,返回结果:
{ "action":"set", "node":{ "key":"/message", "value":"hello world2", "modifiedIndex":48, "createdIndex":48 } }
如果希望客户端A能持续监听,不退出,可以通过增加stream=true参数:
curl "http://127.0.0.1:2379/v2/keys/message?wait=true&stream=true"
当在客户端B执行如下时:
curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="hello world" -d ttl=10
客户端A会实时监听返回,比如当给key设置值,或者当key过期时,客户端A都会监听到:
ETCD安装,请参考文章
《ETCD系列教程2 - ETCD体验》 吕梦楼,公众号:楼仔进阶之路ETCD教程-3.ETCD体验
初探ETCD 3.0
该部分作为扩展知识,仅供了解,更多内容请参考文章
《ETCD系列教程3 - 深入ETCD》 吕梦楼,公众号:楼仔进阶之路ETCD教程-4.深入ETCD
版本说明
目前etcd主要经历了3个大的版本,分别为etcd 0.4版本、etcd 2.0版本和etcd 3.0版本。对于etcd 2.0版本,已经可以很好满足etcd的初步需求,主要包括:
- 专注于key-value存储,而不是一个完整的数据库;
- 通过HTTP + JSON的方式暴露给外部API;
- watch机制提供持续监听某个key变化的功能,以及基于TTL的key的自动过期机制。
但是在实际过程中,我们也发现了一些问题,比如客户端需要频繁地与服务端进行通信,集群在空间和时间上都需要承受较大的压力,以及垃圾回收key的时间不稳定等,同时“微服务”架构要求etcd能够单集群支撑更大规模的并发,因此诞生了etcd 3.0版本,主要对HTTP + JSON的通信方式、key的自动过期机制、watch机制、数据持久化等进行了优化,下面我们看看etcd 3.0版本对每个模块都做了哪些优化。
客户端通信方式
gRPC是Google开源的 个高性能、跨语言的RPC框架,基于HTTP/2协议实现。它使用protobuf作为序列化和反序列化协议,即基于 protobuf 来声明数据模型和RPC接口服务。protobuf的效率远高于JSON,尽管etcd v2的客户端已经对JSON的序列号和反序列化进行了大量的优化,但是etcd v3的gRPC序列号和反序列化的速度依旧是etcd v2的两倍多。
etcdv3的客户端使用gRPC与server进行通信,通信的消息协议使用protobuf进行约定,代替了v2版本的HTTP+JSON格式,使用二进制替代文本,更加节省空间。同时gRPC使用的是HTTP/2协议,同一个连接可以同时处理多个请求,不必像HTTP1.1协议中,多个请求需要建立多个连接。同时,HTTP/2会对请求的Header和请求数据进行压缩编码,常见的有Header帧,用于传输Header内容,另外就是Data帧,来传输正文实体。客户端可以将多个请求放到不同的流中,然后将这些流拆分成帧的形式进行二进制传输,传输的帧也会有一个编号,因此在一个连接中客户端可以发送多个请求,减少了连接数,降低了对服务器的压力,二进制的数据传输格式也会是传输速度更快。
总结一下,其实这里主要进行2点优化:
- 二进制取代字符串:通过gRPC进行通信,代替了v2版本的HTTP+JSON格式;
- 减少TCP连接:使用HTTP/2协议,同一个连接可以同时处理多个请求,摒弃多个请求需要建立多个连接的方式。
键的过期机制
etcdv2中的键的实效是使用TTL机制来实现的,每个有存活时间的键,客户端必须定期的进行刷新重新设置保证它不被自动删除,每次刷新同时还会重新建立连接去更新键。也就是说,及时整个集群都处于空闲状态,也会有很多客户端与服务器进行定期通信,以保证某个key不被自动删除。
etcdv3版本中采用了租约机制进行实现,每个租约会有一个TTL,然后将一些key附加到租约上,当租约到期后,附加到它上边的key都会被删除。利用键的过期机制可以实现服务注册功能,我们可以将一个服务的域名、IP等信息注册到etcd中,并给相应的键设置租约,并在TTL时间内定期维持一个心跳进行刷新。当服务故障后,心跳消失从而相应的键就会自动删除,从而实现了服务的注册功能和服务的健康检查功能。
总结一下,就是v2版本比较傻瓜,需要时刻维护每个key的通信,v3就比较智能,整个统一的过期key的代号,我们把代号称之为“租约”,我们只需要维护这个代号即可,避免客户端去维护所有的key。
watch机制
etcdv2中的键被废除以后,为了能够跟踪key的变化,使用了事件机制进行跟踪,维护键的状态,来防止被删除掉的后键还能恢复和watch到,但是有一个滑动窗口的大小限制,那么如果要获取1000个时间之前的键就获取不到了。因此etcdv2中通过watch来同步数据不是那么可靠,断开连接一段时间后就会导致有可能中间的键的改动获取不到了。在etcdv3中支持get和watch键的任意的历史版本记录。
另外,v2中的watch本质上还是建立很多HTTP连接,每一个watch建立一个tcp套接字连接,当watch的客户端过多的时候会大大消耗服务器的资源,如果有数千个客户端watch数千个key,那么etcd v2的服务端的socket和内存资源会很快被耗尽。v3版本中的watch可以进行连接复用,多个客户端可以共用相同的TCP连接,大大减轻了服务器的压力。
总结一下,其实这里主要进行2点优化:
- 实时监听key的更新:解决v2中途key的数据更新,客服端不会感知的问题;
- 多路复用:这个可以想到select和epool模型,就是一个客户之前需要建立多个TCP连接,现在只需要建立一个即可。
数据存储模型
etcd是一个key-value数据库,ectd v2只保存了key的最新的value,之前的value会被直接覆盖,如果需要知道一个key的历史记录,需要对该key维护一个历史变更的窗口,默认保存最新的1000个变更,但是当数据更新较快时,这1000个变更其实“不够用”,因为数据会被快速覆盖,之前的记录还是找不到。为了解决这个问题,etcd v3摒弃了v2不稳定的“滑动窗口”式设计,引入MVCC机制,采用从历史记录为主索引的存储结构,保存了key的所有历史记录变更,并支持数据在无锁状态下的的快速查询。etcd是一个key-value数据库,etcdv2的key是一个递归的文件目录结构,在v3版本中的键改成了扁平化的数据结构,更加简洁,并通过线段树的优化方式,支持key的快速查询。
由于etcd v3实现了MVCC,保存了每个key-value pair的历史版本,数据大了很多,不能将整个数据库都存放到内存中。因此etcd v3摒弃了内存数据库,转为磁盘数据库,即整个数据都存储在磁盘上,底层的存储引擎使用的是BoltDB。
总结一下,其实这里主要进行3点优化:
- 保存历史数据:摈弃v2的“滑动窗口”式设计,通过MVCC机制,保存了所有的历史数据;
- 数据落磁盘:因为要保存历史数据,数据量态度,不适合全内存存储,使用BoltDB存储;
- 查询优化:摒弃v2的目录式层级化设计,使用线段树优化查询。