Go etcd 的依赖问题终于解决了。。。

解决etcd微服务中的兼容性问题:从v3.3到v3.5的迁移

大家好,我是煎鱼。

前几年非常高频的接触到这一堆微服务相关组件:grpc + grpc-gateway + etcd + protobuf + protoc-gen-go,一开始都是相安无事,逐步跟进新版本。

这不,幺蛾子就来了。写这些组件的开发大佬(或公司)都不在一起,各自为政,各有各的想法、喜欢、规范...因此会出互相不兼容,甚至出现了卡脖子的情况。

b13a5220d300ec1b92e77438fa892b31.png

各种兼容问题

当 etcd 是 v3.3/v3.4,grpc > v1.27 时,经常会遇到各种看着脑壳痛的兼容性问题。

至少但不限于如下几个场景。只是例举几个比较常见的三个兼容错误。

找不到 grpc/naming

找不到 grpc-go 库中的google.golang.org/grpc/naming 包。原因是什么?原因之一是 go.etcd.io/etcd/client 引用到 grpc-go 库中的实验包。

go mod tidy 时,会遇到如下报错:

go: finding module for package google.golang.org/grpc/naming
go: finding module for package google.golang.org/grpc/examples/helloworld/helloworld
go: found google.golang.org/grpc/examples/helloworld/helloworld in google.golang.org/grpc/examples v0.0.0-20231026203026-8cb98464e599
go: finding module for package google.golang.org/grpc/naming
go: git.xxx.cn/xxx/xxx-common/jy imports
 go.etcd.io/etcd/client tested by
 go.etcd.io/etcd/client.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.59.0), but does not contain package google.golang.org/grpc/naming

看到最后的 but does not contain package google.golang.org/grpc/naming。以为是 grpc-go 乱删库,做了不兼容变更。

想着找官方解决一下问题。印象很深刻,人家 grpc-go 表示:我这库早就声明了是实验性,随时可能删除,你不应该依赖他。(不会支持的意思)

找不到 etcd/clientv3/balancer/picker

还是由于 grpc-go 库的实验包在新版本去掉了。会导致 etcd v3.3 出现:undefined: balancer.PickOptionsundefined: resolver.BuildOption 的相关错误信息:

$ go get go.etcd.io/etcd/clientv3
# github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:114:78: undefined: resolver.BuildOption
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:182:31: undefined: resolver.ResolveNowOption
# github.com/coreos/etcd/clientv3/balancer/picker
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/picker/err.go:37:44: undefined: balancer.PickOptions
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/picker/roundrobin_balanced.go:55:54: undefined: balancer.PickOptions

不得不说,这个 BUG 我还给 etcd 提了 issues 和 pr:

c4e36ddb8a514d19d09e329a1415b663.png

最终合并了。(但是 etcd v3.5 当年发布的太慢了,没等到...)

找不到 grpc.SupportPackageIsVersion6

protoc-gen-go 与 grpc 版本不兼容。会出现如下报错:

Getting error undefined: grpc.SupportPackageIsVersion6 and undefined: grpc.ClientConnInterface

本身这个问题,只需要升级 grpc >= 1.27 就可以了。但如果你使用了 etcd sdk,又会前面的 etcd 版本依赖问题,程序会陷入麻烦的升又升不得,降也降不了。

最后还是将 protoc(protoc-gen-go) 降级为 v1.3.2,grpc 保持在 v1.26,这样 etcd v3.3 的依赖才能正常使用。

背后缘由

细心的同学会发现,归根到底还是和 etcd v3.3 扯上关系,grpc 就没法升级到 v1.27 以上。其他所有关联的 protoc、grpc-gateway 的版本都没法继续推进。

当你想用 go module 来做各种兼容管理时,会发现 etcd v3.3 根本没有 go module...

etcd v3.4 虽然有 go.mod,但也无法拉取和使用(原因详见:etcd-io/etcd/issues/11154[1])。

77371c29359dc75f7268ebabd998be05.png

etcd 官方的响应也是不太积极的。猜测是积重难返,比较难解决。

解决方案

社区等了许多年,现在终于有了解决办法。etcd v3.5 已经正式支持了 go module!

etcd 将之前的模块按功能做了领域划分,把之前各种的低版本依赖、循环依赖等问题都处理了。

如下图所示:

70e00822e771bdbb58006c5acd60e4c4.png 64ef020a53f6544d50a4d0b8b4b6f7fa.png

拆分为了 api、client、raft、server、etcdctl、bbolt 等独立的 Go 模块。不会像老版本一样交叉影响。

如果你是新项目,建议无脑使用 etcd v3.5 以上版本。千万别用 v3.3/v3.4 及更低的!

比较无奈的一点

可能有的同学以为皆大欢喜了?其实并不。

他的模块化改造成功仅限于 etcd v3.5 的版本。而历史项目,如果你是使用 etcd v2 store,那么很抱歉。

etcd 老版本(v3.3/v3.4 等)是没有变动的,在 v3.5 的新版本(包含最新的文档)中都在开始在逐步去除 etcd v2 的相关支持。

e73504c470342d9903836ff8ae680d2e.png

如果仍然在使用 etcd v2 的同学,建议进行数据迁移用 v3。这样可以避免很多技术上的问题。

迁移不方便的话,除了各种 replace 和锁版本外。如果你使用的 etcd 功能非常基础,也可以自己实现一个简易版的 SDK。

总结

etcd 的这个历史问题已经存在了好几年,一直处理的慢慢吞吞。甚至影响到了 Go 生态圈的一些技术选型问题。

前几天有同学反馈 tidb 里引用了 cloud.google.com/go/pubsub 库,而该库又依赖了 grpc 的较高的版本。从而导致原有卡在 grpc v1.26 的应用又出现了问题。才回过头来看看。

在新版本中,etcd 的依赖问题终于解决了。真的是,这值得被我们记住!虽然他老版本依然没处理...

推荐阅读

参考资料

[1]

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

关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群👇

40587922ae26d75e4bb72e9bd45c10ba.jpeg

5e470e50edd081a2d14f965a2e79192e.png

你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

### Go语言etcd的集成 #### etcd简介及其用途 Etcd 是一种高度可靠的分布式键值存储,广泛应用于配置管理和服务发现场景中。由于其高可靠性和一致性保障,etcd 成为了众多大型系统(如 Kubernetes)的核心组件之一[^2]。 #### Go语言中的etcd客户端库安装 要在Go项目中引入etcd的支持,可以通过`go get`命令来下载官方提供的客户端包: ```bash go get go.etcd.io/etcd/client/v3 ``` 此操作将会把必要的依赖项加入到项目的模块文件(`go.mod`)当中。 #### 基础功能实现——连接至etcd集群并执行简单CRUD操作 下面给出一段简单的示例代码,演示如何利用Go语言完成对etcd服务器的数据存取工作: ```go package main import ( "context" "fmt" "log" clientv3 "go.etcd.io/etcd/client/v3" ) func main() { cli, err := clientv3.New(clientv3.Config{ Endpoints: []string{"localhost:2379"}, // 替换成实际地址 DialTimeout: 5 * time.Second, }) if err != nil { log.Fatal(err) } defer cli.Close() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) _, err = cli.Put(ctx, "/test/key", "value") cancel() if err != nil { log.Fatal(err) } ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second) resp, err := cli.Get(ctx, "/test/key") cancel() if err != nil { log.Fatal(err) } for _, ev := range resp.Kvs { fmt.Printf("%s : %s\n", ev.Key, ev.Value) } } ``` 上述程序展示了怎样建立与etcd之间的通信链接,并实现了基本的写入和查询动作[^1]。 #### 构建分布式锁机制 对于需要处理竞争条件的应用来说,借助于etcd可以轻松搭建起一套分布式的互斥锁定方案。具体做法如下所示: ```go package main import ( "context" "log" "time" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/concurrency" ) func acquireLock(cli *clientv3.Client, key string) (*concurrency.Session, error) { session, err := concurrency.NewSession(cli) if err != nil { return nil, fmt.Errorf("failed to create session: %v", err) } mutex := concurrency.NewMutex(session, key) if err = mutex.Lock(context.TODO()); err != nil { return nil, fmt.Errorf("failed to lock: %v", err) } return session, nil } func releaseLock(sesn *concurrency.Session) error { if sesn == nil || !sesn.IsOwner() { return errors.New("invalid or expired session") } err := sesn.Destroy(context.TODO()) sesn.Done() return err } // Usage example... cli, _ := clientv3.New(clientv3.Config{Endpoints: []string{"http://localhost:2379"}}) defer cli.Close() sess, err := acquireLock(cli, "/distributed-lock/test-key") if err != nil { log.Fatalf("Failed acquiring the distributed lock: %+v", err) } else { defer func() { _ = releaseLock(sess) }() // Critical section code here... log.Println("Critical section executed successfully.") } ``` 这段脚本说明了创建会话、申请独占权限以及最终释放资源的过程。值得注意的是,在遇到异常情况时应当妥善清理残留状态以防止死锁发生[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值