记录一个 etcd 客户端使用的bug,当时折腾了好久。
本文将会按照安装的过程一步步讲述遇到问题以及如何解决。
etcd clientv3 客户端
首先是 etcd clientv3 的初始化,我们根据指定的 etcd 节点,建立客户端与 etcd 集群的连接。
cli,err := clientv3.New(clientv3.Config{ Endpoints:[]string{"localhost:2379"}, DialTimeout: 5 * time.Second, })
实例化一个 client,这里需要传入的两个参数:
- Endpoints:etcd 的多个节点服务地址,因为我是单点本机测试,所以只传 1 个。
- DialTimeout:创建 client 的首次连接超时,这里传了 5 秒,如果 5 秒都没有连接成功就会返回 err;值得注意的是,一旦 client 创建成功,我们就不用再关心后续底层连接的状态了,client 内部会重连。
Go mod 包依赖冲突处理
我使用了 Go Module 管理依赖。引入相应的第三方依赖,主要是 clientv3 `go.etcd.io/etcd/clientv3`。根据 go.mod 文件来处理依赖关系:
$ Go mod tidy
命令执行完,报了如下的错误:
go: github.com/keets2012/etcd-book-code/client imports go.etcd.io/etcd/clientv3 tested by go.etcd.io/etcd/clientv3.test imports github.com/coreos/etcd/auth imports github.com/coreos/etcd/mvcc/backend imports github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.5: parsing go.mod: module declares its path as: go.etcd.io/bbolt but was required as: github.com/coreos/bbolt
看起来 coreos 维护的 bbolt 库声明它的 module 名称是 go.etcd.io/bbolt ,结果在使用它的时候使用的 package path 是 github.com/coreos/bbolt ,导致不一致( etcd#11720 , etcd#11739 )、 etcd#11749 。
显然是 etcd 使用的是错误的 bbolt 的路径。冷静下来,考虑解决办法。幸好,go module 提供 replace 的方法,我们可以使用下面的方法替换:
replace github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5
然而这样替换之后,还是会出现如下的错误:
go: go.etcd.io/bbolt@v1.3.5 used for two different module paths (github.com/coreos/bbolt and go.etcd.io/bbolt)
按照错误的描述,我们需要再加上一条 replace,使其成为不同的依赖路径。
replace go.etcd.io/bbolt v1.3.5 => github.com/coreos/bbolt v1.3.5
现在我们再执行 `go mod tidy`,bbolt 的问题已经解决了,但是新问题又来了:
github.com/keets2012/etcd-book-code/client imports go.etcd.io/etcd/clientv3 tested by go.etcd.io/etcd/clientv3.test imports github.com/coreos/etcd/integration imports github.com/coreos/etcd/proxy/grpcproxy imports google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.32.0), but does not contain package google.golang.org/grpc/naming
搜一下 issue,你会看到 etcd#11563 、 etcd#11650 、 etcd#11707
Etcd 的代码和新版本的 grpc(v1.27.0)冲突,再次施展替换大法,让项目使用老的 grpc:
replace ( google.golang.org/grpc => google.golang.org/grpc v1.26.0 )
再次执行依赖之间的关系,终于不再报错。检查我们的 go.mod 文件,发现很多的 indirect 引用的库,居然还有两个 etcd :
github.com/coreos/etcd v3.3.25+incompatible go.etcd.io/etcd v3.3.25+incompatible github.com/coreos/bbolt v1.3.5 // indirect
github.com/coreos/bbolt 之所以显示为 indirect 是因为我们的代码中还没有引用这个 package。当引用之后,就可以看到 indirect 标记没了。
我们的项目中引入了 go.etcd.io/etcd,为什么还会有一个 github.com/coreos/etcd?这两个路径的代码应该也是一样。
由此可以判断肯定是某个库引用 github.com/coreos/etcd 了。我们来搜索下是哪个库?go mod 还提供了其他的命令:
go mod why 依赖 //解释为什么需要包或模块 go mod graph 依赖 //输出 module 需求图
$ Go mod why github.com/coreos/etcd go: finding module for package github.com/spf13/cobra go: finding module for package github.com/spf13/pflag go: found github.com/spf13/cobra in github.com/spf13/cobra v1.0.0 go: found github.com/spf13/pflag in github.com/spf13/pflag v1.0.5
或者使用 Go mod graph 查找,执行如下的命令筛选:
Go mod graph |grep github.com/coreos/etcd github.com/keets2012/etcd-book-code github.com/coreos/etcd@v3.3.25+incompatible github.com/spf13/viper@v1.4.0 github.com/coreos/etcd@v3.3.10+incompatible
可以看到是与 github.com/spf13/viper@v1.4.0 相关,github.com/coreos/etcd 使用了 github.com/spf13/cobra@v1.0.0 库, cobra 又使用 viper 的 1.4.0 版本,而 viper@v1.4.0 又使用了 etcd@v3.3.10+incompatible。造成了循环依赖,对于引入依赖来说已经面目全非,原先 go.etcd.io/etcd,现在却成了 github.com/coreos/etcd。
由于 Go module 的一些 bug,以及开源项目使用 Go module 的错误姿势,go module 模式下导致使用 Etcd 代码库困难重重。这里我们已经解决了 etcd clientv3 依赖引入的问题。