vlambda博客
学习文章列表

实现自定义 gRPC Resolver 遇到的问题

package main
import ( "fmt" "strconv")
type Bar struct { Num int}
func (b *Bar) String() string { return strconv.Itoa(b.Num)}
type Foo struct { Num int B *Bar}
func main() { m := map[Foo]int{}
f0 := Foo{Num: 1, B: &Bar{Num: 2}} m[f0] = 0
f1 := Foo{Num: 1, B: &Bar{Num: 2}} m[f1] = 1
fmt.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] = sc b.scStates[sc] = connectivity.Idle sc.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}


对于这个问题,有两个解决方案:

  1. 自己实现一个 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 的解决办法上了。


如果你也遇到了,希望能有帮助。