github:https://github.com/etcd-io/etcd/tree/master/clientv3

测试go版本

  1. go 1.14
  2. require (
  3. go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489
  4. google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc // indirect
  5. google.golang.org/grpc v1.34.0 // indirect
  6. )
  7. replace (
  8. go.etcd.io/etcd => go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 // ae9734ed278b is the SHA for git tag v3.4.13
  9. google.golang.org/grpc => google.golang.org/grpc v1.27.1
  10. )

或者

  1. go 1.14
  2. replace (
  3. github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5
  4. google.golang.org/grpc => google.golang.org/grpc v1.26.0
  5. )
  6. require (
  7. github.com/coreos/bbolt v0.0.0-00010101000000-000000000000 // indirect
  8. github.com/coreos/etcd v3.3.25+incompatible // indirect
  9. github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
  10. github.com/google/uuid v1.2.0 // indirect
  11. github.com/prometheus/client_golang v1.9.0 // indirect
  12. go.etcd.io/etcd v3.3.25+incompatible
  13. go.uber.org/zap v1.16.0 // indirect
  14. golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
  15. google.golang.org/grpc v1.35.0 // indirect
  16. )

grpc和etcd

https://github.com/etcd-io/etcd/issues/12124#issuecomment-674368288
图片.png
bblt

  1. github.com/coreos/etcd/clientv3 tested by
  2. github.com/coreos/etcd/clientv3.test imports
  3. github.com/coreos/etcd/auth imports
  4. github.com/coreos/etcd/mvcc/backend imports
  5. github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.5: parsing go.mod:
  6. module declares its path as: go.etcd.io/bbolt
  7. but was required as: github.com/coreos/bbolt

解决

  1. replace github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5

helloword

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. "go.etcd.io/etcd/clientv3"
  7. )
  8. func main() {
  9. client, err := clientv3.New(clientv3.Config{
  10. Endpoints: []string{"127.0.0.1:2379"}, // 集群列表
  11. DialTimeout: 5 * time.Second,
  12. })
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. kv := clientv3.NewKV(client)
  17. res, err := kv.Put(context.TODO(), "/config/dev/hello", "world", clientv3.WithPrevKV())
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. log.Println("Revision:", res.Header.Revision)
  22. if res.PrevKv != nil {
  23. log.Println("PrevValue:", string(res.PrevKv.Value))
  24. }
  25. }

基本操作

put命令用来设置键值对数据,
get命令用来根据key获取值。
delete命令删除key

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "go.etcd.io/etcd/clientv3"
  7. )
  8. func main() {
  9. cli, err := clientv3.New(clientv3.Config{
  10. Endpoints: []string{"127.0.0.1:2379"},
  11. DialTimeout: 2 * time.Second,
  12. })
  13. if err != nil {
  14. fmt.Printf("connect etcd failed,%v\n", err)
  15. return
  16. }
  17. defer cli.Close()
  18. _, err = cli.Put(context.TODO(), "hello", "etcd")
  19. if err != nil {
  20. fmt.Printf("put etcd failed, %v\n", err)
  21. return
  22. }
  23. resp, err := cli.Get(context.TODO(), "hello")
  24. if err != nil {
  25. fmt.Printf("get etcd failed, %v\n", err)
  26. return
  27. }
  28. for _, kv := range resp.Kvs {
  29. fmt.Printf("%s:%s\n", kv.Key, kv.Value)
  30. }
  31. _, err = cli.Delete(context.TODO(), "hello")
  32. if err != nil {
  33. fmt.Printf("get etcd failed, %v\n", err)
  34. return
  35. }
  36. }

前缀搜索

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. "go.etcd.io/etcd/clientv3"
  7. )
  8. func main() {
  9. client, err := clientv3.New(clientv3.Config{
  10. Endpoints: []string{"127.0.0.1:2379"}, // 集群列表
  11. DialTimeout: 5 * time.Second,
  12. })
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. kv := clientv3.NewKV(client)
  17. _, err = kv.Put(context.TODO(), "/config/hello/dev", "etcd")
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. _, err = kv.Put(context.TODO(), "/config/hello/test", "world")
  22. if err != nil {
  23. log.Fatal(err)
  24. }
  25. res, err := kv.Get(context.TODO(), "/config/hello/", clientv3.WithPrefix())
  26. if err != nil {
  27. log.Fatal(err)
  28. }
  29. log.Println("Revision:", res.Header.Revision)
  30. for _, v := range res.Kvs {
  31. log.Printf("Key=%s,Value=%s\n", string(v.Key), string(v.Value))
  32. }
  33. }

打印结果

  1. Key=/config/hello/dev,Value=etcd
  2. Key=/config/hello/test,Value=world

watch

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "go.etcd.io/etcd/clientv3"
  7. )
  8. func main() {
  9. cli, err := clientv3.New(clientv3.Config{
  10. Endpoints: []string{"127.0.0.1:2379"},
  11. DialTimeout: 2 * time.Second,
  12. })
  13. if err != nil {
  14. fmt.Printf("connect to etcd failed, err:%v\n", err)
  15. return
  16. }
  17. defer cli.Close()
  18. wcs := cli.Watch(context.Background(), "hello") // <-chan WatchResponse
  19. for c := range wcs {
  20. for _, ev := range c.Events {
  21. fmt.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
  22. }
  23. }
  24. }

执行go run watch/main.go
接着执行 go run crud/main.go,在watch/main.go中会出现如下打印

  1. Type: PUT Key:hello Value:etcd
  2. Type: DELETE Key:hello Value:

模拟操作方式

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "time"
  7. "go.etcd.io/etcd/clientv3"
  8. "go.etcd.io/etcd/mvcc/mvccpb"
  9. )
  10. func main() {
  11. client, err := clientv3.New(clientv3.Config{
  12. Endpoints: []string{"127.0.0.1:2379"}, // 集群列表
  13. DialTimeout: 5 * time.Second,
  14. })
  15. if err != nil {
  16. log.Fatal(err)
  17. }
  18. // 模拟etcd中kv的变化
  19. go func() {
  20. for {
  21. client.Put(context.TODO(), "hello", "world")
  22. client.Delete(context.TODO(), "hello")
  23. time.Sleep(2 * time.Second)
  24. }
  25. }()
  26. // 启动监听 5秒后关闭
  27. ctx, cancelFunc := context.WithCancel(context.TODO())
  28. time.AfterFunc(10*time.Second, func() {
  29. cancelFunc()
  30. })
  31. watchRespChan := client.Watch(ctx, "hello")
  32. // 处理kv变化事件
  33. for watchResp := range watchRespChan {
  34. for _, event := range watchResp.Events {
  35. switch event.Type {
  36. case mvccpb.PUT:
  37. fmt.Println("put action", string(event.Kv.Value))
  38. case mvccpb.DELETE:
  39. fmt.Println("delete action", string(event.Kv.Key))
  40. }
  41. }
  42. }
  43. }

租约

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. "go.etcd.io/etcd/clientv3"
  7. )
  8. func main() {
  9. client, err := clientv3.New(clientv3.Config{
  10. Endpoints: []string{"127.0.0.1:2379"}, // 集群列表
  11. DialTimeout: 5 * time.Second,
  12. })
  13. if err != nil {
  14. log.Fatal(err)
  15. }
  16. grant, err := client.Grant(context.TODO(), 10)
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. k := "config/dev/hello"
  21. _, err = client.Put(context.TODO(),
  22. k,
  23. "etcd",
  24. clientv3.WithLease(grant.ID),
  25. )
  26. if err != nil {
  27. log.Fatal(err)
  28. }
  29. var res *clientv3.GetResponse
  30. for {
  31. if res, err = client.Get(context.TODO(), k); err != nil {
  32. log.Println(err)
  33. return
  34. }
  35. if res.Count == 0 {
  36. log.Println("kv已过期")
  37. return
  38. }
  39. log.Println("key未过期")
  40. time.Sleep(2 * time.Second)
  41. }
  42. }

参考

https://github.com/etcd-io/etcd/issues/12464
https://github.com/etcd-io/etcd/issues/11772
https://colobu.com/2020/04/09/accidents-of-etcd-and-go-module/