Docker 搭建 etcd 集群

大数据62

阅读目录:

  • 主机安装
  • 集群搭建
  • API 操作
  • *API 说明和 etcdctl 命令说明

etcd 是 CoreOS 团队发起的一个开源项目(Go 语言,其实很多这类项目都是 Go 语言实现的,只能说很强大),实现了 分布式键值存储服务发现,etcd 和 ZooKeeper/Consul 非常相似,都提供了类似的功能,以及 REST API 的访问操作,具有以下特点:

  • 简单:安装和使用简单,提供了 REST API 进行操作交互
  • 安全:支持 HTTPS SSL 证书
  • 快速:支持并发 10 k/s 的读写操作
  • 可靠:采用 raft 算法,实现分布式系统数据的可用性和一致性

etcd 可以单个实例使用,也可以进行集群配置,因为很多项目都是以 etcd 作为服务发现,比如 CoreOS 和 Kubernetes,所以,下面我们使用 Docker 简单搭建一下 etcd 集群。

Docker 搭建 etcd 集群

1. 主机安装

如果不使用 Docker 的话,etcd 在主机上安装,也非常简单。

Linux 安装命令:

$ curl -L  https://github.com/coreos/etcd/releases/download/v3.3.0-rc.0/etcd-v3.3.0-rc.0-linux-amd64.tar.gz -o etcd-v3.3.0-rc.0-linux-amd64.tar.gz &&
sudo tar xzvf etcd-v3.3.0-rc.0-linux-amd64.tar.gz &&
cd etcd-v3.3.0-rc.0-linux-amd64 &&
sudo cp etcd* /usr/local/bin/

其实就是将编译后的二进制文件,拷贝到 /usr/local/bin/目录,各个版本的二进制文件,可以从 https://github.com/coreos/etcd/releases/ 中查找下载。

Mac OS 安装命令:

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null
$ brew install etcd

执行下面命令,查看 etcd 是否安装成功:

$ etcd --version
etcd Version: 3.2.12
Git SHA: GitNotFound
Go Version: go1.9.2
Go OS/Arch: darwin/amd64

2. 集群搭建

搭建 etcd 集群,需要借助下 Docker Machine 创建三个 Docker 主机,命令:

$ docker-machine create -d virtualbox manager1 &&
docker-machine create -d virtualbox worker1 &&
docker-machine create -d virtualbox worker2

$ docker-machine ls
NAME       ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER        ERRORS
manager1   -        virtualbox   Running   tcp://192.168.99.100:2376           v17.11.0-ce
worker1    -        virtualbox   Running   tcp://192.168.99.101:2376           v17.11.0-ce
worker2    -        virtualbox   Running   tcp://192.168.99.102:2376           v17.11.0-ce

为防止 Docker 主机中垃取官方镜像,速度慢的问题,我们还需要将 etcd 镜像打包推送到私有仓库中,命令:

$ docker tag quay.io/coreos/etcd 192.168.99.1:5000/quay.io/coreos/etcd:latest &&
docker push 192.168.99.1:5000/quay.io/coreos/etcd:latest &&
docker pull 192.168.99.1:5000/quay.io/coreos/etcd:latest

另外,还需要将私有仓库地址配置在 Docker 主机中,并重启三个 Docker 主机,具体配置参考:Docker 三剑客之 Docker Swarm

Docker 主机配置好之后,我们需要使用 docker-machine ssh命令,分别进入三个 Docker 主机中,执行 Docker etcd 配置命令。

manager1 主机( node1 192.168.99.100):

$ docker run -d --name etcd \
    -p 2379:2379 \
    -p 2380:2380 \
    --volume=etcd-data:/etcd-data \
    192.168.99.1:5000/quay.io/coreos/etcd \
    /usr/local/bin/etcd \
    --data-dir=/etcd-data --name node1 \
    --initial-advertise-peer-urls http://192.168.99.100:2380 --listen-peer-urls http://0.0.0.0:2380 \
    --advertise-client-urls http://192.168.99.100:2379 --listen-client-urls http://0.0.0.0:2379 \
    --initial-cluster-state new \
    --initial-cluster-token docker-etcd \
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

worker1 主机( node2 192.168.99.101):

$ docker run -d --name etcd \
    -p 2379:2379 \
    -p 2380:2380 \
    --volume=etcd-data:/etcd-data \
    192.168.99.1:5000/quay.io/coreos/etcd \
    /usr/local/bin/etcd \
    --data-dir=/etcd-data --name node2 \
    --initial-advertise-peer-urls http://192.168.99.101:2380 --listen-peer-urls http://0.0.0.0:2380 \
    --advertise-client-urls http://192.168.99.101:2379 --listen-client-urls http://0.0.0.0:2379 \
    --initial-cluster-state new \
    --initial-cluster-token docker-etcd \
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

worker2 主机( node1 192.168.99.102):

$ docker run -d --name etcd \
    -p 2379:2379 \
    -p 2380:2380 \
    --volume=etcd-data:/etcd-data \
    192.168.99.1:5000/quay.io/coreos/etcd \
    /usr/local/bin/etcd \
    --data-dir=/etcd-data --name node3 \
    --initial-advertise-peer-urls http://192.168.99.102:2380 --listen-peer-urls http://0.0.0.0:2380 \
    --advertise-client-urls http://192.168.99.102:2379 --listen-client-urls http://0.0.0.0:2379 \
    --initial-cluster-state existing \
    --initial-cluster-token docker-etcd \
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

先来说明下 etcd 各个配置参数的意思(参考自 etcd 使用入门):

  • --name:节点名称,默认为 default。
  • --data-dir:服务运行数据保存的路径,默认为 ${name}.etcd
  • --snapshot-count:指定有多少事务(transaction)被提交时,触发截取快照保存到磁盘。
  • --heartbeat-interval:leader 多久发送一次心跳到 followers。默认值是 100ms。
  • --eletion-timeout:重新投票的超时时间,如果 follow 在该时间间隔没有收到心跳包,会触发重新投票,默认为 1000 ms。
  • --listen-peer-urls:和同伴通信的地址,比如 http://ip:2380,如果有多个,使用逗号分隔。需要所有节点都能够访问,所以不要使用 localhost!
  • --listen-client-urls:对外提供服务的地址:比如 http://ip:2379,http://127.0.0.1:2379,客户端会连接到这里和 etcd 交互。
  • --advertise-client-urls:对外公告的该节点客户端监听地址,这个值会告诉集群中其他节点。
  • --initial-advertise-peer-urls:该节点同伴监听地址,这个值会告诉集群中其他节点。
  • --initial-cluster:集群中所有节点的信息,格式为 node1=http://ip1:2380,node2=http://ip2:2380,&#x2026;,注意:这里的 node1 是节点的 --name 指定的名字;后面的 ip1:2380 是 --initial-advertise-peer-urls 指定的值。
  • --initial-cluster-state:新建集群的时候,这个值为 new;假如已经存在的集群,这个值为 existing。
  • --initial-cluster-token:创建集群的 token,这个值每个集群保持唯一。这样的话,如果你要重新创建集群,即使配置和之前一样,也会再次生成新的集群和节点 uuid;否则会导致多个集群之间的冲突,造成未知的错误。

上述配置也可以设置配置文件,默认为 /etc/etcd/etcd.conf

我们可以使用 docker ps,查看 Docker etcd 是否配置成功:

$ docker ps
CONTAINER ID        IMAGE                                   COMMAND                  CREATED             STATUS              PORTS                              NAMES
463380d23dfe        192.168.99.1:5000/quay.io/coreos/etcd   "/usr/local/bin/et..."   2 hours ago         Up 2 hours          0.0.0.0:2379-2380->2379-2380/tcp   etcd

然后进入其中一个 Docker 主机:

$ docker exec -it etcd bin/sh

执行下面命令(查看集群成员):

$ etcdctl member list
773d30c9fc6640b4: name=node2 peerURLs=http://192.168.99.101:2380 clientURLs=http://192.168.99.101:2379 isLeader=true
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=false
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

可以看到,集群里面有三个成员,并且 node2为管理员, node1node3为普通成员。

etcdctl 是 ectd 的客户端命令工具(也是 go 语言实现),里面封装了 etcd 的 REST API 执行命令,方便我们进行操作 etcd,后面再列出 etcdctl 的命令详细说明。

上面命令的 etcd API 版本为 2.0,我们可以手动设置版本为 3.0,命令:

$ export ETCDCTL_API=3 && /usr/local/bin/etcdctl put foo bar
OK

部分命令和执行结果还是和 2.0 版本,有很多不同的,比如同是查看集群成员,3.0 版本的执行结果:

$ etcdctl member list
773d30c9fc6640b4, started, node2, http://192.168.99.101:2380, http://192.168.99.101:2379
b2b0bca2e0cfcc19, started, node3, http://192.168.99.102:2380, http://192.168.99.102:2379
c88e2cccbb287a01, started, node1, http://192.168.99.100:2380, http://192.168.99.100:2379

好了,我们现在再演示一种情况,就是从集群中移除一个节点,然后再把它添加到集群中,为演示 etcd 中使用 Raft 算法,我们将 node2管理节点,作为操作对象。

我们在随便一个主机 etcd 容器中( node2除外),执行成员移除集群命令(必须使用 ID,使用别名会报错):

$ etcdctl member remove 773d30c9fc6640b4
Member 773d30c9fc6640b4 removed from cluster f84185fa5f91bdf6

我们再执行下查看集群成员命令(v2 版本):

$ etcdctl member list
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=true
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

会发现 node2管理节点被移除集群了,并且通过 Raft 算法, node3被推举为管理节点。

在将 node2节点重新加入集群之前,我们需要执行下面命令:

$ etcdctl member add node2 --peer-urls="http://192.168.99.101:2380"
Member 22b0de6ffcd98f00 added to cluster f84185fa5f91bdf6

ETCD_NAME="node2"
ETCD_INITIAL_CLUSTER="node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380,node1=http://192.168.99.100:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

可以看到,ETCD_INITIAL_CLUSTER_STATE 值为 existing,也就是我们配置的 --initial-cluster-state参数。

我们再执行下查看集群成员命令(v2 版本):

$ etcdctl member list
22b0de6ffcd98f00[unstarted]: peerURLs=http://192.168.99.101:2380
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=true
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

会发现 22b0de6ffcd98f00成员状态变为了 unstarted

我们在 node2节点,执行 Docker etcd 集群配置命令:

$ docker run -d --name etcd \
    -p 2379:2379 \
    -p 2380:2380 \
    --volume=etcd-data:/etcd-data \
    192.168.99.1:5000/quay.io/coreos/etcd \
    /usr/local/bin/etcd \
    --data-dir=/etcd-data --name node2 \
    --initial-advertise-peer-urls http://192.168.99.101:2380 --listen-peer-urls http://0.0.0.0:2380 \
    --advertise-client-urls http://192.168.99.101:2379 --listen-client-urls http://0.0.0.0:2379 \
    --initial-cluster-state existing \
    --initial-cluster-token docker-etcd \
    --initial-cluster node1=http://192.168.99.100:2380,node2=http://192.168.99.101:2380,node3=http://192.168.99.102:2380

结果并不像我们想要的那样成功,执行查看日志:

$ docker logs etcd
2017-12-25 08:19:30.160967 I | etcdmain: etcd Version: 3.2.12
2017-12-25 08:19:30.161062 I | etcdmain: Git SHA: b19dae0
2017-12-25 08:19:30.161082 I | etcdmain: Go Version: go1.8.5
2017-12-25 08:19:30.161092 I | etcdmain: Go OS/Arch: linux/amd64
2017-12-25 08:19:30.161105 I | etcdmain: setting maximum number of CPUs to 1, total number of available CPUs is 1
2017-12-25 08:19:30.161144 N | etcdmain: the server is already initialized as member before, starting as etcd member...

2017-12-25 08:19:30.161195 I | embed: listening for peers on http://0.0.0.0:2380
2017-12-25 08:19:30.161232 I | embed: listening for client requests on 0.0.0.0:2379
2017-12-25 08:19:30.165269 I | etcdserver: name = node2
2017-12-25 08:19:30.165317 I | etcdserver: data dir = /etcd-data
2017-12-25 08:19:30.165335 I | etcdserver: member dir = /etcd-data/member
2017-12-25 08:19:30.165347 I | etcdserver: heartbeat = 100ms
2017-12-25 08:19:30.165358 I | etcdserver: election = 1000ms
2017-12-25 08:19:30.165369 I | etcdserver: snapshot count = 100000
2017-12-25 08:19:30.165385 I | etcdserver: advertise client URLs = http://192.168.99.101:2379
2017-12-25 08:19:30.165593 I | etcdserver: restarting member 773d30c9fc6640b4 in cluster f84185fa5f91bdf6 at commit index 14
2017-12-25 08:19:30.165627 I | raft: 773d30c9fc6640b4 became follower at term 11
2017-12-25 08:19:30.165647 I | raft: newRaft 773d30c9fc6640b4 [peers: [], term: 11, commit: 14, applied: 0, lastindex: 14, lastterm: 11]
2017-12-25 08:19:30.169277 W | auth: simple token is not cryptographically signed
2017-12-25 08:19:30.170424 I | etcdserver: starting server... [version: 3.2.12, cluster version: to_be_decided]
2017-12-25 08:19:30.171732 I | etcdserver/membership: added member 773d30c9fc6640b4 [http://192.168.99.101:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.171845 I | etcdserver/membership: added member c88e2cccbb287a01 [http://192.168.99.100:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.171877 I | rafthttp: starting peer c88e2cccbb287a01...

2017-12-25 08:19:30.171902 I | rafthttp: started HTTP pipelining with peer c88e2cccbb287a01
2017-12-25 08:19:30.175264 I | rafthttp: started peer c88e2cccbb287a01
2017-12-25 08:19:30.175339 I | rafthttp: added peer c88e2cccbb287a01
2017-12-25 08:19:30.178326 I | etcdserver/membership: added member cbd7fa8d01297113 [http://192.168.99.102:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.178383 I | rafthttp: starting peer cbd7fa8d01297113...

2017-12-25 08:19:30.178410 I | rafthttp: started HTTP pipelining with peer cbd7fa8d01297113
2017-12-25 08:19:30.179794 I | rafthttp: started peer cbd7fa8d01297113
2017-12-25 08:19:30.179835 I | rafthttp: added peer cbd7fa8d01297113
2017-12-25 08:19:30.180062 N | etcdserver/membership: set the initial cluster version to 3.0
2017-12-25 08:19:30.180132 I | etcdserver/api: enabled capabilities for version 3.0
2017-12-25 08:19:30.180255 N | etcdserver/membership: updated the cluster version from 3.0 to 3.2
2017-12-25 08:19:30.180430 I | etcdserver/api: enabled capabilities for version 3.2
2017-12-25 08:19:30.183979 I | rafthttp: started streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.184139 I | rafthttp: started streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.184232 I | rafthttp: started streaming with peer c88e2cccbb287a01 (stream MsgApp v2 reader)
2017-12-25 08:19:30.185142 I | rafthttp: started streaming with peer c88e2cccbb287a01 (stream Message reader)
2017-12-25 08:19:30.186518 I | etcdserver/membership: removed member cbd7fa8d01297113 from cluster f84185fa5f91bdf6
2017-12-25 08:19:30.186573 I | rafthttp: stopping peer cbd7fa8d01297113...

2017-12-25 08:19:30.186614 I | rafthttp: started streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186786 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186815 I | rafthttp: started streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186831 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (writer)
2017-12-25 08:19:30.186876 I | rafthttp: started streaming with peer cbd7fa8d01297113 (stream MsgApp v2 reader)
2017-12-25 08:19:30.187224 I | rafthttp: started streaming with peer cbd7fa8d01297113 (stream Message reader)
2017-12-25 08:19:30.187647 I | rafthttp: stopped HTTP pipelining with peer cbd7fa8d01297113
2017-12-25 08:19:30.187682 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (stream MsgApp v2 reader)
2017-12-25 08:19:30.187873 I | rafthttp: stopped streaming with peer cbd7fa8d01297113 (stream Message reader)
2017-12-25 08:19:30.187895 I | rafthttp: stopped peer cbd7fa8d01297113
2017-12-25 08:19:30.187911 I | rafthttp: removed peer cbd7fa8d01297113
2017-12-25 08:19:30.188034 I | etcdserver/membership: added member b2b0bca2e0cfcc19 [http://192.168.99.102:2380] to cluster f84185fa5f91bdf6
2017-12-25 08:19:30.188059 I | rafthttp: starting peer b2b0bca2e0cfcc19...

2017-12-25 08:19:30.188075 I | rafthttp: started HTTP pipelining with peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.188510 I | rafthttp: started peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.188533 I | rafthttp: added peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.188795 I | etcdserver/membership: removed member 773d30c9fc6640b4 from cluster f84185fa5f91bdf6
2017-12-25 08:19:30.193643 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.193730 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.193797 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (stream MsgApp v2 reader)
2017-12-25 08:19:30.194782 I | rafthttp: started streaming with peer b2b0bca2e0cfcc19 (stream Message reader)
2017-12-25 08:19:30.195663 I | raft: 773d30c9fc6640b4 [term: 11] received a MsgHeartbeat message with higher term from b2b0bca2e0cfcc19 [term: 12]
2017-12-25 08:19:30.195716 I | raft: 773d30c9fc6640b4 became follower at term 12
2017-12-25 08:19:30.195736 I | raft: raft.node: 773d30c9fc6640b4 elected leader b2b0bca2e0cfcc19 at term 12
2017-12-25 08:19:30.196617 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.197064 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.197846 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.198242 E | rafthttp: streaming request ignored (ID mismatch got 22b0de6ffcd98f00 want 773d30c9fc6640b4)
2017-12-25 08:19:30.201771 E | etcdserver: the member has been permanently removed from the cluster
2017-12-25 08:19:30.202060 I | etcdserver: the data-dir used by this member must be removed.

2017-12-25 08:19:30.202307 E | etcdserver: publish error: etcdserver: request cancelled
2017-12-25 08:19:30.202338 I | etcdserver: aborting publish because server is stopped
2017-12-25 08:19:30.202364 I | rafthttp: stopping peer b2b0bca2e0cfcc19...

2017-12-25 08:19:30.202482 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.202504 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (writer)
2017-12-25 08:19:30.204143 I | rafthttp: stopped HTTP pipelining with peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.204186 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (stream MsgApp v2 reader)
2017-12-25 08:19:30.204205 I | rafthttp: stopped streaming with peer b2b0bca2e0cfcc19 (stream Message reader)
2017-12-25 08:19:30.204217 I | rafthttp: stopped peer b2b0bca2e0cfcc19
2017-12-25 08:19:30.204228 I | rafthttp: stopping peer c88e2cccbb287a01...

2017-12-25 08:19:30.204241 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.204255 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (writer)
2017-12-25 08:19:30.204824 I | rafthttp: stopped HTTP pipelining with peer c88e2cccbb287a01
2017-12-25 08:19:30.204860 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (stream MsgApp v2 reader)
2017-12-25 08:19:30.204878 I | rafthttp: stopped streaming with peer c88e2cccbb287a01 (stream Message reader)
2017-12-25 08:19:30.204891 I | rafthttp: stopped peer c88e2cccbb287a01

这么长的日志,说明啥问题呢,就是说我们虽然重新执行的 etcd 创建命令,但因为读取之前配置文件的关系,etcd 会恢复之前的集群成员,但之前的集群节点已经被移除了,所以集群节点就一直处于停止状态。

怎么解决呢?很简单,就是将我们之前创建的 etcd-data数据卷轴删掉,命令:

$ docker volume ls
DRIVER              VOLUME NAME
local               etcd-data

$ docker volume rm etcd-data
etcd-data

然后,再在 node2节点,重新执行 Docker etcd 集群配置命令(上面),会发现执行是成功的。

我们再执行下查看集群成员命令(v2 版本):

$ etcdctl member list
22b0de6ffcd98f00: name=node2 peerURLs=http://192.168.99.101:2380 clientURLs=http://192.168.99.101:2379 isLeader=false
b2b0bca2e0cfcc19: name=node3 peerURLs=http://192.168.99.102:2380 clientURLs=http://192.168.99.102:2379 isLeader=true
c88e2cccbb287a01: name=node1 peerURLs=http://192.168.99.100:2380 clientURLs=http://192.168.99.100:2379 isLeader=false

3. API 操作

etcd REST API 被用于键值操作和集群成员操作,这边就简单说几个,详细的 API 查看附录说明。

1. 键值管理

设置键值命令:

$ curl http://127.0.0.1:2379/v2/keys/hello -XPUT -d value="hello world"
{"action":"set","node":{"key":"/hello","value":"hello world","modifiedIndex":17,"createdIndex":17}}

查看键值命令:

$ curl http://127.0.0.1:2379/v2/keys/hello
{"action":"get","node":{"key":"/hello","value":"hello world","modifiedIndex":17,"createdIndex":17}}

删除键值命令:

$ curl http://127.0.0.1:2379/v2/keys/hello -XDELETE
{"action":"delete","node":{"key":"/hello","modifiedIndex":19,"createdIndex":17},"prevNode":{"key":"/hello","value":"hello world","modifiedIndex":17,"createdIndex":17}}

2. 成员管理

列出集群中的所有成员:

$ curl http://127.0.0.1:2379/v2/members
{"members":[{"id":"22b0de6ffcd98f00","name":"node2","peerURLs":["http://192.168.99.101:2380"],"clientURLs":["http://192.168.99.101:2379"]},{"id":"b2b0bca2e0cfcc19","name":"node3","peerURLs":["http://192.168.99.102:2380"],"clientURLs":["http://192.168.99.102:2379"]},{"id":"c88e2cccbb287a01","name":"node1","peerURLs":["http://192.168.99.100:2380"],"clientURLs":["http://192.168.99.100:2379"]}]}

查看当前节点是否为管理节点:

$ curl http://127.0.0.1:2379/v2/stats/leader
{"leader":"b2b0bca2e0cfcc19","followers":{"22b0de6ffcd98f00":{"latency":{"current":0.001051,"average":0.0029195000000000002,"standardDeviation":0.001646769458667484,"minimum":0.001051,"maximum":0.006367},"counts":{"fail":0,"success":10}},"c88e2cccbb287a01":{"latency":{"current":0.000868,"average":0.0022389999999999997,"standardDeviation":0.0011402923601720172,"minimum":0.000868,"maximum":0.004725},"counts":{"fail":0,"success":12}}}}

查看当前节点信息:

$ curl http://127.0.0.1:2379/v2/stats/self
{"name":"node3","id":"b2b0bca2e0cfcc19","state":"StateLeader","startTime":"2017-12-25T06:00:28.803429523Z","leaderInfo":{"leader":"b2b0bca2e0cfcc19","uptime":"36m45.45263851s","startTime":"2017-12-25T08:13:02.103896843Z"},"recvAppendRequestCnt":6,"sendAppendRequestCnt":22}

查看集群状态:

$ curl http://127.0.0.1:2379/v2/stats/store
{"getsSuccess":9,"getsFail":4,"setsSuccess":9,"setsFail":0,"deleteSuccess":3,"deleteFail":0,"updateSuccess":0,"updateFail":0,"createSuccess":7,"createFail":0,"compareAndSwapSuccess":0,"compareAndSwapFail":0,"compareAndDeleteSuccess":0,"compareAndDeleteFail":0,"expireCount":0,"watchers":0}

当然也可以通过 API 添加和删除集群成员。

4. API 说明和 etcdctl 命令说明

etcd REST API 说明(v2 版本):

命令 说明 curl -L
http://127.0.0.1:2379/version

查看版本 curl
http://127.0.0.1:2379/v2/keys/message

-XPUT -d value="Hello world" 添加键值 curl
http://127.0.0.1:2379/v2/keys/message

获取键值 curl
http://127.0.0.1:2379/v2/keys/message

-XPUT -d value="Hello etcd" 更新键值 curl
http://127.0.0.1:2379/v2/keys/message

-XDELETE 删除键值 curl
http://127.0.0.1:2379/v2/keys/foo

-XPUT -d value=bar -d ttl=5 添加 TTL 键值(过期时间) curl
http://127.0.0.1:2379/v2/keys/foo

获取 TTL 键值 curl
http://127.0.0.1:2379/v2/keys/foo

-XPUT -d value=bar -d ttl= -d prevExist=true 更新 TTL 键值 curl
http://127.0.0.1:2379/v2/keys/foo?wait=true

curl
http://127.0.0.1:2379/v2/keys/foo

-XPUT -d value=bar curl '
http://127.0.0.1:2379/v2/keys/foo?wait=true&waitIndex=7

' curl
http://127.0.0.1:2379/v2/keys/queue

-XPOST -d value=Job1 curl -s '
http://127.0.0.1:2379/v2/keys/queue?recursive=true&sorted=true

' curl
http://127.0.0.1:2379/v2/keys/dir

-XPUT -d ttl=30 -d dir=true curl
http://127.0.0.1:2379/v2/keys/dir

-XPUT -d ttl=30 -d dir=true -d prevExist=true curl '
http://127.0.0.1:2379/v2/keys/dir?wait=true

' curl
http://127.0.0.1:2379/v2/keys/dir

-XPUT -d dir=true 创建目录 curl
http://127.0.0.1:2379/v2/keys/foo_dir/foo

-XPUT -d value=bar 在目录下添加键值 curl
http://127.0.0.1:2379/v2/keys/?recursive=true

获取目录下的键值 curl '
http://127.0.0.1:2379/v2/keys/foo_dir?dir=true

' -XDELETE 删除目录 curl
http://10.0.0.10:2379/v2/members

查看集群成员 curl
http://10.0.0.10:2379/v2/members

-XPOST -H "Content-Type: application/json" -d '{"peerURLs":["
http://10.0.0.10:2380

"]}' 添加集群成员 curl
http://10.0.0.10:2379/v2/members/272e204152

-XDELETE 删除集群成员 curl
http://10.0.0.10:2379/v2/members/272e204152

-XPUT -H "Content-Type: application/json" -d '{"peerURLs":["
http://10.0.0.10:2380

"]}' 更新集群成员

更多 API 请查看:etcd APIMembers API

etcdctl 命令说明:

命令 说明 etcdctl set key value 添加键值 etcdctl get key 获取键值 etcdctl update key value 更新键值 etcdctl rm key 删除键值 etcdctl mkdir dirname 添加目录(不存在的话创建) etcdctl setdir 添加目录(都创建) etcdctl updatedir 更新目录 etcdctl rmdir 删除目录 etcdctl ls 列出目录 etcdctl watch 监控键值 etcdctl exec-watch 监控键值(执行命令) etcdctl list 查看集群成员 etcdctl member add 添加集群成员 etcdctl remove 移除集群成员

参考资料:

Original: https://www.cnblogs.com/xishuai/p/docker-etcd.html
Author: 田园里的蟋蟀
Title: Docker 搭建 etcd 集群



相关阅读1

Title: PHP 开发者的 Docker 之旅

Docker 搭建 etcd 集群

用 PHP 作为我们「Docker 开发大礼包」开篇是带着一些朝圣的心情的。这是一门堪称「古老」的语言,这也是一门争议最多的语言,这更是一门不断涅槃的语言。「PHP 是最好的语言」这个流传已久的梗,或许正是对我国最有群众基础的编程语言描述里,最经典的注解。

就让我们一起回顾一下 PHP 的发展历程作为此系列文章的开篇。历史是最好的老师,他给每个未来提供启示。

谁创造了 PHP?

Rasmus Lerdorf 在 1994 年创造了 PHP,Andi Gutmans 和 Zeev Suraski 之后于 1997 年重写了 PHP 的解析器,之后他们在以色列成立了著名的 Zend 公司来维护 PHP 的开发。

PHP 是什么的缩写?

起先的意思直白的有点近乎粗鲁「Personal Home Page」,后来改成了「PHP: Hypertext Preprocessor」即便用今天的眼光看也是很酷炫的命名。

PHP 最著名的版本?

PHP 5 发布于 2004 年 7 月 13 日,距今正好第十一个年头。

下一个 PHP 版本?

PHP 7 计划于 2015 年 11 月 12 日发布。

PHP 的形象代言人?

一只蓝色的大象,因为「elePHPant」(黄色的那头是 Hadoop)。

谁在主导 PHP 的方向?

PHP Group 来维护其标准,Zend 作为商业公司提供参考实现。

HHVM 又是什么?

HipHop Virtual Machine (HHVM) 是 Facebook 对 PHP 的一次革命性衍进,HHVM 采用实时编译进程,使得 PHP 的执行效率大幅提升。Facebook 在 2013 年全面采用 HHVM 运行 PHP,间接拉升了 PHP 生态的逼格,激发了整个 PHPer 群体的活力。

PHP 的包依赖怎么做?

Composer

PHP 的单元测试怎么做?

PHPUnit

常见的 PHP 技术栈和应用场景?

LAMP(Linux + Apache + MySQL + PHP)长期占据着我国互联网应用的主流架构。即便在今天,这也是电商和社区类互联网公司的主要技术栈。尤其在电商领域,说 PHP 托起半个中国的购物车都不为过。

为什么 ASP 和 JSP 都没有 PHP 受众广?

当年 FTP 比起 VPS 简直是白菜价。但这也间接养成了 PHP 开发重功能轻工程的通病,加上部署的随意性,上规模后运维虐成狗。

就这个运维问题,作者 Rasmus Lerdorf 怎么看?

I'm not a real programmer. I throw together things until it works then I move on. The real programmers will say "Yeah it works but you're leaking memory everywhere. Perhaps we should fix that." I'll just restart Apache every 10 requests.

翻译过来就是:「重启应该就有好运气」。

那怎么破的,能教教吗?

上 Docker!

欢迎进入「PHP 应用 Docker 开发大礼包 - Powered by DaoCloud」,六篇由浅入深、精心设计的系列文章,带领 PHP 开发者领略 Docker 化应用开发和发布的全新体验。

https://www.oschina.net/question/2322629_244177

Original: https://www.cnblogs.com/chen110xi/p/7324380.html
Author: chen110xi
Title: PHP 开发者的 Docker 之旅

相关阅读2

Title: 58_Option数据结构 及 优雅的处理null问题

1. 什么是 Option(选项)

是Scala 提供的一种数据类型
    用来封装一个数据 Option(var)
        当var有值时 就返回 Some(var)
        当var为空时 就返回 None

2. Option API

object OptionApi extends App {
  //TODO 初始化 option 对象
  val v1: Any = null
  val v2: Any = 100
  private val op1: Option[Any] = Option(v1)
  private val op2: Option[Any] = Option(v2)

  println(op1) // None
  println(op2) // Some(100)

  //TODO 获取 option中的值

  //方式1: 直接获取 Some中的值(不推荐)
  //println(op1.get) //java.util.NoSuchElementException: None.get
  println(op2.get) // 100

  //方式2: 通过模式匹配来获取值(推荐)
  var op1v: Any = op1 match {
    case Some(s) => s
    case None => 999
  }

  var op2v: Any = op2 match {
    case Some(s) => s
    case None => 999
  }

  println(op1v) // 999
  println(op2v) // 100

  //方式3: 通过getOrElse方法获取值(推荐)
  var op11 = op1.getOrElse(999)
  var op22 = op2.getOrElse(999)
  println(op11) // 999
  println(op22) // 100

  // TODO 判断 option 是否为null
  private val opIfEmp1: Boolean = op1.isEmpty
  private val opIfEmp2: Boolean = op2.isEmpty
  println(opIfEmp1) // true
  println(opIfEmp2) // false

}

3. 最佳实践

3.1 操作map 根据key 获取 value

object GetValueByKey extends App {
  val map: Map[String, String] = Map("id" -> "1", "name" -> "奉先")

  private val op1: Option[String] = map.get("id")
  private val op2: Option[String] = map.get("id1")

  //通过 key 获取value的值(key不存在时,获取默认值)
  println(op1.getOrElse("default")) //1
  println(map.get("name").getOrElse("default")) // 奉先
  println(map.getOrElse("no", "default")) //default
}

3.2 处理方法中的 null

object GraceOperateNull extends App {
  def getConcatStr(separator: String, argu: Any*): String = {
    argu.map(
      Option(_).getOrElse("None") // 判断 参数_ 是否为null,如果为null 给定默认值None
        .toString).mkString(separator)
  }

  println(getConcatStr("_", "a", "b", "c", 1, 1.23, null))
  //a_b_c_1_1.23_None
}

Original: https://www.cnblogs.com/bajiaotai/p/16455000.html
Author: 学而不思则罔!
Title: 58_Option数据结构 及 优雅的处理null问题

相关阅读3

Title: 什么是谓词下推,看这一篇就够了

今天有个小伙伴问我,什么是谓词下推,然后我就开启巴拉巴拉模式,说了好长一段时间,结果发现他还是懵的。

最后我概述给他一句话:所谓谓词下推, 就是将尽可能多的判断更贴近数据源,以使查询时能跳过无关的数据。用在SQL优化上来说,就是先过滤再做聚合等操作。

看到这里的朋友可能就已经明白了什么是谓词下推,如果仅为了解有啥用,看到这里就可以退出了,如果想告诉别人这是个啥(高大上)那且听我细细道来。

要理解谓词下推,应该从两个方面来看,即谓词和下推两部分。

1.什么是谓词

predicate push down 翻译为谓词下推,这个翻译很准确,明确的告诉了我们这个操作是一个什么动作,但是为人诟病的是,什么是谓词,结合起来是什么意思,就比较难以理解。

predicate push down 又可以叫做 Filter Push down,这个叫法准确的描述了动作,但没有精准定位什么能被称之为Filter。全局来看,还是predicate push down较为准确。

predicate(谓词)即条件表达式,在SQL中, 谓词就是返回boolean值即true和false的函数,或是隐式转换为bool的函数。SQL中的谓词主要有 LKIE、BETWEEN、IS NULL、IS NOT NULL、IN、EXISTS其结果为布尔值,即true或false。

谓词的使用场景:在SELECT语句的WHERE子句或HAVING子句中,确定哪些行与特定查询相关。 ps:并非所有谓词都可以在HAVING子句中使用。

那么反过来想,是不是在以上的场景中使用的,用来判断true或false的就是谓词呢?是的!

这样是不是就可以很好的理解了什么是谓词。

2.什么是下推

理解了什么是谓词后,我们再看看什么是下推,哪里被称为下,哪里被称为上呢?看图说话。

Docker 搭建 etcd 集群

如图, 下是table_A和table_B,即数据源头

上是Result,即数据结果。

蓝色部分是未采用谓词下推运算过程,黄色部分是采用了谓词下推的运算过程。

3.什么是谓词下推

看到这里,我们可能已经理解了什么是谓词下推,基本的意思 predicate pushdown 是将SQL语句中的部分语句( predicates 谓词部分) 可以被 " pushed" 下推到数据源或者靠近数据源的部分。

根据上图对比可以看出通过尽早过滤掉数据,这种处理方式能大大减少数据处理的量,降低资源消耗,在同样的服务器环境下,极大地减少了查询/处理时间。

在Hive SQL和Spark SQL等一系列SQL ON Hadoop的解析语法树时都在谓词下推方面作出了优化,其实在使用SQL的过程,我们心中记住这是一种将过滤尽可能在靠近数据源(取数据)的时候完成的一种操作,是数据库的一种经典的优化手段。

我们平时的SQL优化手段中,谓词下推也是一种被频繁使用的方式,简洁且有效。

4.一些常见的应用

4.1传统数据库应用

在传统数据库的查询系统中谓词下推作为优化手段很早就出现了,谓词下推的目的就是通过将一些过滤条件尽可能的在最底层执行可以减少每一层交互的数据量,从而提升性能。

例如下面这个例子:

select count(0) from A Join B on A.id = B.id
where A.a > 10 and B.b < 100;

通过查看执行计划,可以看到,在处理Join操作之前需要首先对A和B执行TableScan操作,然后再进行Join,再执行过滤,最后计算聚合函数返回,但是如果把过滤条件 A.a > 10B.b < 100分别移到A表的TableScan和B表的TableScan的时候执行,可以大大降低Join操作的输入数据。

优化后的语句如下:

select count(0) from (select *  from A  where a>10) A1
Join (
    select *  from B  where b
4.2Hive中的谓词下推

下面说说Hive中的谓词下推(同样适用于SparkSQL)

Hive中的Predicate Pushdown简称谓词下推,简而言之,就是在不影响结果的情况下,尽量将过滤条件提前执行。谓词下推后,过滤条件在map端执行,减少了map端的输出,降低了数据在集群上传输的量,节约了集群的资源,也提升了任务的性能。

具体配置项是 hive.optimize.ppd,默认为true,即开启谓词下推。

PPD规则:

规则的逻辑描述如下:

During Join predicates cannot be pushed past Preserved Row tables( join条件过滤不能下推到保留行表中)

比如以下选择,left join中左表s1为保留行表(先扫描s1表,即以左表为基表),所以on条件(join过滤条件)不能下推到s1中

select s1.key, s2.key from src s1 left join src s2 on s1.key > '2';

而s2表不是保留行,所以s2.key>2条件可以下推到s2表中:

select s1.key, s2.key from src s1 left join src s2 on s2.key > '2';

After Join predicates cannot be pushed past Null Supplying tables(where条件过滤不能下推到NULL补充表)

比如以下选择left join的右表s2为NULL补充表所以,s1.key>2 where条件可以下推到s1:

select s1.key, s2.key from src s1 left join src s2 where s1.key > '2';

而以下选择由于s2未NULL补充表所以s2.key>2过滤条件不能下推

select s1.key, s2.key from src s1 left join src s2 where s2.key > '2';

关于join和where,PPD采用的规则如下:

实验结果表格形式:

Docker 搭建 etcd 集群

总结如下:

  • 对于Join(Inner Join)、Full outer Join,条件写在on后面,还是where后面,性能上面没有区别;
  • 对于Left outer Join ,右侧的表写在on后面、左侧的表写在where后面,性能上有提高;
  • 对于Right outer Join,左侧的表写在on后面、右侧的表写在where后面,性能上有提高;
  • 当条件分散在两个表时,谓词下推可按上述结论2和3自由组合;
  • 所谓下推,即谓词过滤在map端执行;所谓不下推,即谓词过滤在reduce端执行

注意:如果在表达式中含有不确定函数,整个表达式的谓词将不会被pushed,例如

select a.* from a join b on a.id = b.id
where a.ds = '2022-08-15' and a.create_time = unix_timestamp();

因为 unix_timestamp是不确定函数,在编译的时候无法得知,所以,整个表达式不会被pushed,即 ds='2022-08-15'也不会被提前过滤。类似的不确定函数还有 rand()等。

4.3列式存储中的谓词下推

无论是行式存储还是列式存储,都可以在将过滤条件在读取一条记录之后执行以判断该记录是否需要返回给调用者,在ORC File和Parquet文件存储格式中又利用该思想做了更进一步的优化。

以Parquet为例,优化的方法是对每一个Row Group的每一个Column Chunk在存储的时候都计算对应的统计信息,包括该Column Chunk的最大值、最小值和空值个数。通过这些统计值和该列的过滤条件可以判断该Row Group是否需要扫描。另外Parquet未来还会增加诸如Bloom Filter和Index等优化数据,更加有效的完成谓词下推。

在使用Parquet的时候可以通过如下两种策略提升查询性能:1、类似于关系数据库的主键,对需要频繁过滤的列设置为有序的,这样在导入数据的时候会根据该列的顺序存储数据,这样可以最大化的利用最大值、最小值实现谓词下推。2、减小行组大小和页大小,这样增加跳过整个行组的可能性,但是此时需要权衡由于压缩和编码效率下降带来的I/O负载。

ORC File也是类似的操作,具体在讲解ORC File时详细说明。

RF算法中,用了谓词下推思想。大小表进行broadcast hash join时,用小表的join列数据构建BloomFilter,广播到大表的所有partition,使用该BloomFilter对大表join列数据进行过滤。最后将大表过滤后得到的数据与小表数据进行hashJoin。

这个过程如下图:

Docker 搭建 etcd 集群

这样的好处是:

  • 在存储层即过滤了大量大表无效数据,减少扫描无效数据列的同行其他列数据IO
  • 减少存储进程到计算进程传输的数据
  • 减少hashjoin开销

例如如下sql:

select item.name, order.* from order , item where order.item_id = item.id and item.category = 'book'

使用谓词下推,会将表达式 item.category = &#x2018;book&#x2019;下推到join条件 order.item_id = item.id之前。

再往高大上的方面说,就是将过滤表达式下推到存储层直接过滤数据,减少传输到计算层的数据量。

以上,就是完整的谓词下推理解了。

参考资料:
https://blog.csdn.net/strongyoung88/article/details/81156271

上一期:
Hive存储格式之RCFile详解,RCFile的过去现在未来

下期预告:Hive存储格式之ORC File详解

按例,我的个人公众号:鲁边社,点击关注

后台回复关键字 hive,随机赠送一本鲁边备注版珍藏大数据书籍。

Original: https://www.cnblogs.com/lubians/p/16590243.html
Author: 鲁边
Title: 什么是谓词下推,看这一篇就够了