etcd API-V2

etcd API V2


获取etcd版本

curl -L http://127.0.0.1:2379/version

设置etcd的key的value

V2版本的key在etcd是按照目录格式来存储的:

[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="Hello world"

{"action":"set","node":{"key":"/message","value":"Hello world","modifiedIndex":4,"createdIndex":4}}

返回属性:

1.action:就是刚才我们执行的请求操作,XPUT对应的是set
2.node.key :对应的是我们设置的key,以“/”作为开头
3.value:代表的是如果命令执行成功,key的值
4.node.createdIndex : 这个其实是raft内部的一个单调递增的值,对于etcd数据的更改操作都会分配一个index,所以可以看到,一开始etcd启动的时候,不是从0而是从4开始的,这是因为etcd内部的一些消息交互(时钟之类)造成的。
5.node.modifiedIndex:set, delete, update, create, compareAndSwap and compareAndDelete 这些操作都会造成这个值的变动。

对于createdIndex 和 modifiedIndex两个值,基本上是一直相等的,当你-XPUT的时候,会同步更新这两个值,但delete和update的时候这两个值就会不同了,只有modifiedIndex会增加。

消息头

etcd的回复里面,包含了简单的消息头,里面的数据也比较重要。

[root@compile-server bin]#  curl http://127.0.0.1:2379/v2/keys/message -vv
* About to connect() to 127.0.0.1 port 2379 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 2379 (#0)
> GET /v2/keys/message HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:2379
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< X-Etcd-Cluster-Id: cdf818194e3a8c32
< X-Etcd-Index: 31
< X-Raft-Index: 33
< X-Raft-Term: 2
< Date: Fri, 10 May 2019 08:43:37 GMT
< Content-Length: 102
<
{"action":"get","node":{"key":"/message","value":"Hello world","modifiedIndex":31,"createdIndex":31}}
* Connection #0 to host 127.0.0.1 left intact

对于etcd的header:
    1.X-Etcd-Index  代表的是上面说的"modifiedIndex"。watch的时候默认就是从这个index后面开始的。
    2.X-Raft-Index 代表的是底层的raft的协议
    3.X-Raft-Term 代表的是raft的任期的概念。在raft里面这个和logindex是很重要的两个标志位,对于选举有重大意义(raft协议规定选主必须满足数据最新+term最大)

读取数据

数据读取比较简单,直接读取某个key就可以。

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

[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/message
{"action":"get","node":{"key":"/message","value":"Hello world","modifiedIndex":33,"createdIndex":33}}

更改key的值

和设置一样,更改的时候用也是PUT操作

curl http://127.0.0.1:2379/v2/keys/message -XPUT -d value="Hello etcd"
{"action":"set","node":{"key":"/message","value":"Hello etcd","modifiedIndex":34,"createdIndex":34},"
prevNode":{"key":"/message","value":"Hello world","modifiedIndex":33,"createdIndex":33}}

更改key的value的时候,可以看到有个preNode,代表的是我们在执行该操作之前,node的状态。如果之前不存在这个key,那么就不存在preNode.

删除key

[root@compile-server bin]#  curl http://127.0.0.1:2379/v2/keys/message -XDELETE
{"action":"delete","node":{"key":"/message","modifiedIndex":35,"createdIndex":34},"prevNode":{"key":"/message","value":"Hello etcd","modifiedIndex":34,"createdIndex":34}}

删除操作和更改操作基本一致,不再讲解。

使用key的TTL

TTL就是给key设置一个timeout,时间到了之后,key会被自动删除,当然你可以在超时之前删除timeout,这样key就不会有超期了。同样的你也可以不断的去刷新key的timeout,这样可以起到看门狗的作用(可以用来探测etcd的存活)。

curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -d ttl=5 

//ttl代表的是超时时间,这里是5秒。

curl http://127.0.0.1:2379/v2/keys/foo //这里是读取key

{"action":"get","node":{"key":"/foo","value":"bar","expiration":"2019-05-12T03:09:23.606325102Z","ttl":1,"modifiedIndex":39,"createdIndex":39}}

//在这里可以到有个expiration,代表的是从什么时间点开始的,ttl是代表剩余的时间。当5s超时之后再用get指令。

{"errorCode":100,"message":"Key not found","cause":"/foo","index":40}
//返回的是100,key not found.

[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -d ttl= -d prevExist=true
//这里是删除ttl的命令,这样foo这个key就不会有超时机制了。
{"action":"update","node":{"key":"/foo","value":"bar","modifiedIndex":53,"createdIndex":52},"prevNode":{"key":"/foo","value":"bar","exon":"2019-05-12T03:12:14.138970921Z","ttl":1,"modifiedIndex":52,"createdIndex":52}}

注意这里的action是udate,createdIndex是不会增加的,modifiedIndex是增加的。

需要注意的一点是,timeout只会由leader来触发,如果一个instance(不是leader)因为某种原因退出集群了,那么该instance的超时key就不会删除了,除非它重新加入集群,leader会帮它删掉(个人认为这时候不再是timeout删除,而是raft底层的数据同步,会把删除操作append到节点,节点会删除数据.如果leader退出了,那么新的leader会执行timeout操作。

更新key的TTL

上面说过,在超时之前可以用命令重新更新key的ttl。

curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -d ttl=5 //设置key
curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d ttl=5 -d refresh=true -d prevExist=true //更新key的ttl,这里有个问题refresh和preExist分别代表什么?作用是什么?

//首先设置了一个key
[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d ttl=30
{"action":"set","node":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:12.864653611Z","ttl":30,"modifiedIndex":104,"createdIndex":104}}

//加了refresh之后,返回的action是“set”,这个地方应该会有点疑惑,因为我们用的refresh,理论上应该是update呀??
[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d ttl=30 -d refresh=true
{"action":"set","node":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:33.419839799Z","ttl":30,"modifiedIndex":105,"createdIndex":105},"prevNode":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:12.864653611Z","ttl":10,"modifiedIndex":104,"createdIndex":104}}

//这个地方用是prevExist,检查是否有这个key,等于true可以理解为强制写入,默认的是true,如果为false,二次写入会报错。
[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d ttl=30 -d prevExist=true
{"action":"update","node":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:45.027170827Z","ttl":30,"modifiedIndex":106,"createdIndex":105},"prevNode":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:33.419839799Z","ttl":19,"modifiedIndex":105,"createdIndex":105}}

[root@compile-server bin]# curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d ttl=30 -d refresh=true -d prevExist=true
{"action":"update","node":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:54.905998913Z","ttl":30,"modifiedIndex":107,"createdIndex":105},"prevNode":{"key":"/foo","value":"","expiration":"2019-05-12T03:40:45.027170827Z","ttl":21,"modifiedIndex":106,"createdIndex":105}}

watch功能

watch功能是etcd里面一个很重要的功能,现在有v2和v3两个版本。分别对应了不同的设计,可以参照下面的链接:
https://blog.csdn.net/zl1zl2zl3/article/details/79627412

我们可以通过常轮训来监听key的变化,同时可以使用recursive=true参数来对子key(目录)监听。

//启动监听,会一直等待,直到收到事件
curl http://127.0.0.1:2379/v2/keys/foo?wait=true
//设置key,上面会收到该事件消息
curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar
//可以看到收到事件信息,第一次设置该key只会显示本次的操作,如果再监听一次可以看到上次的操作。
{"action":"set","node":{"key":"/foo","value":"bar","modifiedIndex":22,"createdIndex":22}}
 //因为etcd会保存最近的1000条记录,如果直接加上waitIndex则直接返回上次的数据,这里选择上面的modifiedIndex,是不会阻塞等待的
curl 'http://127.0.0.1:2379/v2/keys/foo?wait=true&waitIndex=22'
//如果选择一个比目前小的waitIndex(比如选择10),则会返回9之后最早发生的一次。
curl 'http://127.0.0.1:2379/v2/keys/foo?wait=true&waitIndex=10'
//17是最早的一次改动,所以就会返回最早的一次
{"action":"set","node":{"key":"/foo","value":"","modifiedIndex":17,"createdIndex":17}}

//recursive参数
curl 'http://127.0.0.1:2379/v2/keys/dir/?wait=true&recursive=true'
curl 'http://127.0.0.1:2379/v2/keys/dir/?wait=true'

两个watch命令都是watch同一个目录,但第一个用了recursive,也就是会监听目录下面所有的key的变化。下面的命令,如果操作目录下的key,不会触发事件。

注意:

etcd的watch智能保持100条记录(V2版本,v3没这个限制),所以收到通知之后应该将response交给其它线程去处理,不要在watch线程里处理,不然容易阻塞!!

从已经清空的Index监听

前面已经说过etcd的v2版本只能保存1000个事件,如果已经事件丢失,我们需要先得到目前的状态,然后再开始监听。比如我们对一个key设置了2000次,那么etcd里面保存的直邮1000-2000的事件,如果这时候我们用下面命令:

curl 'http://127.0.0.1:2379/v2/keys/foo?wait=true&waitIndex=8'
 //我们将会收到下面的错误
{"errorCode":401,"message":"The event in requested index is outdated and cleared","cause":"the requested history has been cleared [1008/8]","index":2007}

那现在我们怎么开始监听这个key那,因为我们也不知道etcd里面保存的事件是从哪里开始的。所以我们可以使用下面命令得到etcd现在的状态。

curl 'http://127.0.0.1:2379/v2/keys/foo' -vv

< HTTP/1.1 200 OK
< Content-Type: application/json
< X-Etcd-Cluster-Id: 7e27652122e8b2ae
< X-Etcd-Index: 2007
< X-Raft-Index: 2615
< X-Raft-Term: 2
< Date: Mon, 05 Jan 2015 18:54:43 GMT
< Transfer-Encoding: chunked
<
{ "action":"get","node":{"key":"/foo","value":"bar","modifiedIndex":7,"createdIndex":7}}

前面已经解释过返回的信息的标志位的意思了。X-Etcd-Index代表的是etcd的状态,modifiedIndex代表的是上次修改这个key的index,X-Etcd-Index是大于等于modifiedIndex的,理论上来讲用两个index + 1是相同效果,但是用modifiedIndex可能会导致上面说的401error,所以我们一般都用X-Etcd-Index。

连接关闭

连接关闭指的是可能server还没来的及发送event,但由于超时或者server发生了关机,因为http的header是一旦接收到连接就会返回头部的,所以返回值会是200:ok和空的消息体。client需要自己处理这种情况。

创建自动增长的顺序keys

在一个目录下使用POST可以创建出顺序的key,有时候这个功能很有用,比如需要严格按照顺序处理的事件。可以保证cleint可以公平的访问mutex锁(这个点没太搞懂)

创建一个顺序的key很简单:

    curl http://127.0.0.1:2379/v2/keys/queue -XPOST -d value=Job1
    //返回值
    {
        "action": "create",
        "node": {
            "createdIndex": 6,
            "key": "/queue/00000000000000000006",
            "modifiedIndex": 6,
            "value": "Job1"
        }
    }

    //获取目录下所有的顺序key的命令

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

这里的key的后缀是全局的etcd的index。

目录的TTL

etcd的存储是以目录树的结构来的,所以我们可以对某个目录设置一个TTL。

    //设置一个目录
    curl http://127.0.0.1:2379/v2/keys/dir -XPUT -d ttl=30 -d dir=true
    
    //刷新目录的ttl
    curl http://127.0.0.1:2379/v2/keys/dir -XPUT -d ttl=30 -d dir=true -d prevExist=true    

目录的ttl超时的时候,如果有监听线程正在监听它下面的key,那么watch会收到一个expire的事件。

etcd的CAS操作

etcd的 Compare-and-Swap

etcd主要通过三个标志位来对etcd的数据进行原子操作,注意etcd的原子操作只能针对key,不能针对目录,否则会返回102“Not a file” error。

1.prevValue - 检查key的上一个状态.

2.prevIndex -检查key的modifiedIndex.(保证没别的线程修改过)

3.prevExist - 如果是true,代表"update"请求,否则是create请求。

下面看几个例子:

    curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=one

    curl http://127.0.0.1:2379/v2/keys/foo?prevExist=false -XPUT -d value=three
    返回值:
    {
        "cause": "/foo",
        "errorCode": 105,
        "index": 39776,
        "message": "Key already exists"
    }

    curl http://127.0.0.1:2379/v2/keys/foo?prevValue=two -XPUT -d value=three
    //返回值:
    {
        "cause": "[two != one]",
        "errorCode": 101,
        "index": 8,
        "message": "Compare failed"
    }

    curl http://127.0.0.1:2379/v2/keys/foo?prevValue=one -XPUT -d value=two

    //返回值

    {
        "action": "compareAndSwap",
        "node": {
            "createdIndex": 8,
            "key": "/foo",
            "modifiedIndex": 9,
            "value": "two"
        },
        "prevNode": {
            "createdIndex": 8,
            "key": "/foo",
            "modifiedIndex": 8,
            "value": "one"
        }
    }

etcd的Compare-and-Delete

etcd的删除操作也支持原子操作,标志位和上面的比少了prevExist。其它的是一样的。看例子:

    curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=one
    
    //因为value不一致,所以不能删除。
    curl http://127.0.0.1:2379/v2/keys/foo?prevValue=two -XDELETE
    //返回值
    {
        "errorCode": 101,
        "message": "Compare failed",
        "cause": "[two != one]",
        "index": 8
    }
    //prevIndex不一致,所以不能删除。
    curl http://127.0.0.1:2379/v2/keys/foo?prevIndex=1 -XDELETE
    
    //返回值
    {
        "errorCode": 101,
        "message": "Compare failed",
        "cause": "[1 != 8]",
        "index": 8
    }

    curl http://127.0.0.1:2379/v2/keys/foo?prevValue=one -XDELETE

    {
        "action": "compareAndDelete",
        "node": {
            "key": "/foo",
            "modifiedIndex": 9,
            "createdIndex": 8
        },
        "prevNode": {
            "key": "/foo",
            "value": "one",
            "modifiedIndex": 8,
            "createdIndex": 8
        }
    }

创建目录

在大多数情况下,目录都会在创建key的时候自动创建,但有时候我们需要自己去创建一个目录或者删除一个目录。用参数dir=true就可以了。

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

{
    "action": "set",
    "node": {
        "createdIndex": 30,
        "dir": true,
        "key": "/dir",
        "modifiedIndex": 30
    }
}

列出目录

在etcd中,如果想获得目录下所有的key,可以用下面的命令:

curl http://127.0.0.1:2379/v2/keys/?recursive=true

如果没有recursive,那么只会显示目录,不会现在目录下面的key或者子目录。

删除目录

删除etcd的目录的时候,如果目录下面有key或者别的目录存在,你删除的时候会返回下面的错误:

{"errorCode":108,"message":"Directory not empty","cause":"/dir2","index":156}

所以如果想直接删除整个目录,可以用下面的命令(recursive=true)

curl 'http://127.0.0.1:2379/v2/keys/dir2?recursive=true' -XDELETE

创建隐藏的node

etcd可以通过添加_前缀创建隐藏的key或者目录,这时候你通过GET指令是看不到这个node的。

etcd隐藏的node主要作用是为了安全性,特地去github上看了一下作者的解释。

    The hidden was designed for security through obscurity. So you cannot find the hidden key from API which is by design. You can find it if you can access the local disk though.

https://github.com/etcd-io/etcd/issues/6375

就是说隐藏key你通过etcd的api是看不到的,除非你能登陆本地的disk,然后查看本地的存储文件。保密性高,我们可以将我们一些key放在里面。创建的指令如下:

    curl http://127.0.0.1:2379/v2/keys/_message -XPUT -d value="Hello hidden world"

通过文件设置key

我们可以用etcd存储一些小的配置文件。

echo "Hello\nWorld" > afile.txt
curl http://127.0.0.1:2379/v2/keys/afile -XPUT --data-urlencode value@afile.txt

线性化读取

大家都知道etcd的写操作是都要经过raft协议同步的,线性化读取也是同样的道理。如果 Get的时候使用参数quorum=true,那么GET操作也要走raft协议,不是本地直接返回。

统计

etcd的状态统计

我们可以查看etcd的一些内部统计信息来观察etcd的状态。主要就分为etcd的状态和存储状态。

    //获取etcd的leader状态
    curl http://127.0.0.1:2379/v2/stats/leader
    
    //获取自己的状态       
    curl http://127.0.0.1:2379/v2/stats/self

每个标志位的含义:

    id: the unique identifier for the member
    
    leaderInfo.leader: id of the current leader member
    
    leaderInfo.uptime: amount of time the leader has been leader
    
    name: this member's name
    
    recvAppendRequestCnt: number of append requests this node has processed
    
    recvBandwidthRate: number of bytes per second this node is receiving (follower only)
    
    recvPkgRate: number of requests per second this node is receiving (follower only)
    
    sendAppendRequestCnt: number of requests that this node has sent
    
    sendBandwidthRate: number of bytes per second this node is sending (leader only). This value is undefined on single member clusters.
    
    sendPkgRate: number of requests per second this node is sending (leader only). This value is undefined on single member clusters.
    
    state: either leader or follower
    
    startTime: the time when this node was started

etcd的存储统计

    curl http://127.0.0.1:2379/v2/stats/store

    {
        "compareAndSwapFail": 0,
        "compareAndSwapSuccess": 0,
        "createFail": 0,
        "createSuccess": 2,
        "deleteFail": 0,
        "deleteSuccess": 0,
        "expireCount": 0,
        "getsFail": 4,
        "getsSuccess": 75,
        "setsFail": 2,
        "setsSuccess": 4,
        "updateFail": 0,
        "updateSuccess": 0,
        "watchers": 0
    }

etcd的存储的状态是存储在内存中的,所以重启之后是会重置各个参数。

https://coreos.com/etcd/docs/latest/v2/api.html

结束语

掌握etcd的API是使用etcd的前提条件,所以把v2版本的按照个人理解和操作整理出来。后面会整理v3版本的API。

推荐阅读更多精彩内容