01.etcd安装使用
# 01.etcd
# 1.1 mac安装etcd
// 用brew安装非常方便,没安装的自行安装Homebrew
brew install etcd
// 执行etcd即可启动服务
etcd
1
2
3
4
2
3
4
# 1.2 etcd基本命令
- 基本操作
$ etcdctl put name zhangsan
$ etcdctl get name # 获取key和value
$ etcdctl del name
$ etcdctl update name lisi
$ etcdctl get name --print-value-only # 只获value,不获取key
1
2
3
4
5
2
3
4
5
- 前缀获取
$ etcdctl put foo1 bar1
$ etcdctl put foo2 bar2
$ etcdctl put foo3 bar3
# 获取从foo到foo3的值,不包括foo3
$ etcdctl get foo foo3 --print-value-only
bar1
bar2
# 获取前缀为foo的值
$ etcdctl get --prefix foo --print-value-only
bar1
bar2
bar3
# 获取符合前缀的前两个值
$ etcdctl get --prefix --limit=2 foo --print-value-only
bar
bar1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
事务写入
etcdctl put user1 bad
etcdctl txn --interactive
compares:
value("user1") = "bad"
success requests (get, put, delete):
del user1
failure requests (get, put, delete):
put user1 good
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
分布式锁
- 通过指定的名字加锁
- 注意,只有当正常退出且释放锁后,lock命令的退出码是0,否则这个锁会一直被占用直到过期(默认60秒)
- 使用Ctrl+C正常退出lock命令,退出码为0,第二次能正常lock
- 如果是系统命令 kill 的
ps aux|grep 'etcdctl lock'
退出码是非0的
$ etcdctl lock test
test/38015a3fd6795e04
$ echo $?
0
1
2
3
4
2
3
4
# 1.3 watch监视值变化
$ etcdctl watch name // 使用watch指令监控name变化
$ etcdctl put name lisi // 在另外一个终端修改,可以扛到watch打印信息
1
2
2
# 1.4 获取版本信息
- etcd使用b+tree存放key的版本信息
$ etcdctl put test 1
$ etcdctl put test 2
$ etcdctl put test 3
$ etcdctl get test -w json
{
"header":{
"cluster_id":14841639068965178418,
"member_id":10276657743932975437,
"revision":9, // 版本号
"raft_term":3
},
"kvs":[
{
"key":"dGVzdA==",
"create_revision":7,
"mod_revision":9,
"version":3,
"value":"Mw=="
}
],
"count":1
}
$ etcdctl get test --rev=9
test
3
$ etcdctl get test --rev=8
test
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.5 etcd事务
$ etcdctl put addr bj
$ etcdctl put addr sh
$ etcdctl get addr -w json
/*
内存中 B树 key对应revision
磁盘中 B+树 revison 找到value
*/
{
"header":{
"cluster_id":14841639068965178418,
"member_id":10276657743932975437,
"revision":12, // 全局版本号 64位整数,只要对etcd修改,版本号都会加一(只要修改任意一个key都会加一)
"raft_term":4 // 任期 64位整数 全局单调递增(每个leader都会对应一个任期)
},
"kvs":[
{
"key":"YWRkcg==",
"create_revision":11, // 创建key时候的revision版本号(和上面revision对应的)
"mod_revision":12, // 修改key时候的revision版本号(和上面revision对应的)
"version":2,
"value":"c2g="
}
],
"count":1
}
$ etcdctl get addr --rev=12 // 获取版本号为12的值
addr
sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
value("addr") 根据当前key的值来判断
$ etcdctl txn -i
compares: // 判断条件
value("addr") = "sh"
success requests (get, put, del): // 如果判断条件成功,走这里的语句
put addr gz
get addr
failure requests (get, put, del): // 如果判断条件不成立,走这里的语句
del addr
SUCCESS // 这里可以看到走的是成功块的语句
OK
addr
gz
$ etcdctl get addr
addr
gz
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mod(key) key的修改版本号
# 1.6 lease租约
类似 Redis expire
大量key过期会导致性能下降,这时候可以将相同过期时间的key绑定到同一个对象中
只要对象过期,这些可以就过期,这个对象就是租约
// 1)新建一个过期时间为120s的租约
# etcdctl lease grant 120
lease 018f6d7bb11aba0d granted with TTL(120s)
// 2)查看新建的租约信息
# etcdctl lease list
found 1 leases
018f6d7bb11aba0d
// 3)新建key,并为该key指定租约
# etcdctl put name alice --lease="018f6d7bb11aba0d"
// 4)查看当前租约绑定了哪些key
# etcdctl lease timetolive 018f6d7bb11aba0d --keys
// 5)等到租约过期后,再次查看租约已经过期,对应的key也已经被自动删除
# etcdctl lease timetolive 018f6d7bb11aba0d --keys
lease 018f6d7bb11aba0d already expired
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
续租
// 1)新建租约并赋予key值
# etcdctl lease grant 30
lease 018f6d7bd032c117 granted with TTL(30s)
# etcdctl put name alice --lease=018f6d7bd032c117
// 2)在租约即将过期时进行续约,命令不会自动结束,会一直显示该窗口
# etcdctl lease keep-alive 018f6d7bd032c117
lease 018f6d7bd032c117 keepalived with TTL(30)
lease 018f6d7bd032c117 keepalived with TTL(30)
lease 018f6d7bd032c117 keepalived with TTL(30)
// 3)重新打开一个窗口,查看该租约续约信息,可以看到该租约会自动续约,相应key值也不会被删除
# etcdctl lease timetolive 018f6d7bd032c117 --keys
lease 018f6d7bd032c117 granted with TTL(30s), remaining(23s), attached keys([name])
# etcdctl lease timetolive 018f6d7bd032c117 --keys
lease 018f6d7bd032c117 granted with TTL(30s), remaining(22s), attached keys([name])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
回收租约:自动删除与该租约关联的key
// 1)回收租约
# etcdctl lease revoke 018f6d7bd032c117
// 2)可以看到租约已经过期
# etcdctl lease timetolive 018f6d7bd032c117 --keys
lease 018f6d7bd032c117 already expired
1
2
3
4
5
2
3
4
5
# 02.go基本操作
# 2.1 get和put
package main
import (
"context"
"fmt"
"time"
"go.etcd.io/etcd/clientv3"
)
func main() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
fmt.Printf("connect to etcd failed, err:%v\n", err)
return
}
fmt.Println("connect to etcd success")
defer cli.Close()
// put
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err = cli.Put(ctx, "lmh", "lmh")
cancel()
if err != nil {
fmt.Printf("put to etcd failed, err:%v\n", err)
return
}
// get
ctx, cancel = context.WithTimeout(context.Background(), time.Second)
resp, err := cli.Get(ctx, "lmh")
cancel()
if err != nil {
fmt.Printf("get from etcd failed, err:%v\n", err)
return
}
fmt.Println(resp)
for _, ev := range resp.Kvs {
fmt.Printf("%s:%s\n", ev.Key, ev.Value)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 2.2 watch操作
- watch用来获取未来更改的通知。
package main
import (
"context"
"fmt"
"time"
"go.etcd.io/etcd/clientv3"
)
func main() {
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
fmt.Printf("connect to etcd failed, err:%v\n", err)
return
}
fmt.Println("connect to etcd success")
defer cli.Close()
// watch key:lmh change
rch := cli.Watch(context.Background(), "lmh") // <-chan WatchResponse
for wresp := range rch {
for _, ev := range wresp.Events {
fmt.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
将上面的代码保存编译执行,此时程序就会等待etcd中lmh这个key的变化。
例如:我们打开终端执行以下命令修改、删除、设置lmh这个key。
etcd> etcdctl.exe --endpoints=http://127.0.0.1:2379 put lmh "lmh1"
OK
etcd> etcdctl.exe --endpoints=http://127.0.0.1:2379 del lmh
1
etcd> etcdctl.exe --endpoints=http://127.0.0.1:2379 put lmh "lmh2"
OK
1
2
3
4
5
6
2
3
4
5
6
- 上面的程序都能收到如下通知
watch>watch.exe
connect to etcd success
Type: PUT Key:lmh Value:lmh1
Type: DELETE Key:lmh Value:
Type: PUT Key:lmh Value:lmh2
1
2
3
4
5
2
3
4
5
上次更新: 2024/10/15 16:27:13