实现自定义 gRPC Resolver 遇到的问题
package mainimport ("fmt""strconv")type Bar struct {Num int}func (b *Bar) String() string {return strconv.Itoa(b.Num)}type Foo struct {Num intB *Bar}func main() {m := map[Foo]int{}f0 := Foo{Num: 1, B: &Bar{Num: 2}}m[f0] = 0f1 := Foo{Num: 1, B: &Bar{Num: 2}}m[f1] = 1fmt.Println(m)}// output:// map[{1 2}:0 {1 2}:1]
正式开始,我们知道 gRPC 支持自定义 Resolver 来实现自定义的服务发现机制,自定义 Balancer 来实现自定义的负载均衡策略。
假如我们要实现一个基于权重的负载均衡策略,通常这么做:
type Address struct {// Addr is the server address on which a connection will be established.Addr string// Attributes contains arbitrary data about this address intended for// consumption by the load balancing policy.Attributes *attributes.Attributes// 省略了其他不重要的字段}
其中,Attributes 字段可以用来保存负载均衡策略所使用的信息,比如权重信息。
再实现一个 Balancer 来根据 Resolver 提供的实例权重来做负载均衡。
gRPC 提供了一个 baseBalancer 封装了一些通用的更新连接状态的逻辑,同时抽象了一个 Picker 的接口,可以简单理解为它是用来定义如何从 Resolver 提供的服务实例中选择一个可用实例。所以,通常如果没有特殊需求的话,我们只需要实现一个 Picker 就可以,将实现的 Picker 和 baseBalancer 组合就可以生成一个新的 Balancer。而这次遇到的问题就是出在这个 baseBalancer 和 Address 定义上。
在 gRPC v1.35.0 之前的版本,baseBalancer 对连接的管理是这样实现的:
func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {// 省略部分代码...// addrsSet is the set converted from addrs, it's used for quick lookup of an address.addrsSet := make(map[resolver.Address]struct{})for _, a := range s.ResolverState.Addresses {addrsSet[a] = struct{}{}if _, ok := b.subConns[a]; !ok {// a is a new address (not existing in b.subConns).sc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{HealthCheckEnabled: b.config.HealthCheck})if err != nil {logger.Warningf("base.baseBalancer: failed to create new SubConn: %v", err)continue}b.subConns[a] = scb.scStates[sc] = connectivity.Idlesc.Connect()}}for a, sc := range b.subConns {// a was removed by resolver.if _, ok := addrsSet[a]; !ok {b.cc.RemoveSubConn(sc)delete(b.subConns, a)// Keep the state of this sc in b.scStates until sc's state becomes Shutdown.// The entry will be deleted in UpdateSubConnState.}}// 省略一些不重要的代码...return nil}
对于这个问题,有两个解决方案:
自己实现一个 Balancer,把 resolver.Address.Addr 作为 map 的 Key,权重不参与比较,可以单独保存;
考虑到方法2实现上相对简单,所以我们采用了方案 2。
但是当我们把 gRPC 升级到 v1.35.0 及以上的版本时,发现服务实例的权重信息丢失了。查看v1.35.0 的 Release Notes 时找到了相关的一个 pr #4024 (https://github.com/grpc/grpc-go/pull/4024),修复了上面说到的问题,在使用 resolver.Address 作为 baseBalancer.subConns map 的 key 之前,先去掉了 Attributes 字段(设置为 nil)。但是,我们实现的 Picker 是通过 baseBalancer.subConns 的 key 来获取实现权重,然后实现按权重选择实例的。但是目前 Attributes 被去掉了,导致权重丢失。只能回到方案 1 的解决办法上了。
如果你也遇到了,希望能有帮助。
