0

    gRPC-go源码剖析二之grpc服务器端启动时都做了哪些事情

    2023.08.04 | admin | 127次围观

    这次分享一下当grpc服务器在启动时都做了什么事情?

    可以自己先思考一下,假设让我们自己去开发一个简单版本的grpc服务器端启动时都会做什么事情呢?

    a.一些初始化工作

    b.监听某个端口

    c.注册服务端提供的服务

    。。。。。

    好了,接下来看一下grpc-go框架服务器端启动时的流程图:

    在下面的章节中只是介绍了常用的初始化组件,有些功能需要手动显示的调用,或者import导入才能初始化或者注册,比方说grpc-go/encoding/gzip/gzip.go文件中的gzip压缩器需要手动导入,因此就不再一一介绍了。

    一个链接请求,对应一个http2Server对象,一个帧接收器,一个帧发送器;

    1、注册、初始化工作

    下面几个小节,仅仅列出了grpc-go源码中哪些文件实现了注册、初始化等工作。

    至于详细原理介绍,会再后面的章节中分享。

    1.1、注册服务

    通过下面的形式,可以将提供的服务注册到grpc服务器端,以供客户端调用;

    这里我们以源码中自带的heloworld为例,将SayHello服务注册到grpc服务器端:

    1.2、解析器初始化

    在grpc框架里内置了几种解析器,也可以自定义解析器;

    像passthrough、dns解析器在grpc服务器启动时会自己注册;

    像manual、xds解析器,需要在代码里显示注册才生效;

    1.2.1、passthrough解析器(默认使用,启动时自己注册)

    .../internal/resolver/passthrough/passthrough.go文件中

    func init() {
       resolver.Register(&passthroughBuilder{})
    }
    

    1.2.2、dns解析器(启动时自己注册)

    .../internal/resolver/dns/dns_resolver.go文件中

    func init() {
       resolver.Register(NewBuilder())
    }
    

    1.2.3、Manual解析器(需要手动显示的注册)

    .../resolver/manual/manual.go文件中

    func GenerateAndRegisterManualResolver() (*Resolver, func()) {
       scheme := strconv.FormatInt(time.Now().UnixNano(), 36)
       r := NewBuilderWithScheme(scheme)
       resolver.Register(r)
       return r, func() { resolver.UnregisterForTesting(scheme) }
    }
    

    如果想要使用manual解析器的话,需要手动显示的注册一下,如下参考例子:

    .../examples/features/health/client/main.go文件

    1.func main() {
    2.   flag.Parse()
    3.   r, cleanup := manual.GenerateAndRegisterManualResolver()
    4.   defer cleanup()
    5.   r.InitialState(resolver.State{
    6.      Addresses: []resolver.Address{
    7.         {Addr: "localhost:50051"},
    8.         {Addr: "localhost:50052"},
    9.      },
    10.   })
    11.   address := fmt.Sprintf("%s:///unused", r.Scheme())
    // -------省略不相关代码------
    

    核心代码说明:

    1.第3行,手动注册指定解析器

    .../examples/features/debugging/client/main.go文件:

    1.func main() {
    2.   /***** Set up the server serving channelz service. *****/
    3.   lis, err := net.Listen("tcp", ":50052")
    4.   if err != nil {
    5.      log.Fatalf("failed to listen: %v", err)
    6.   }
    7.   defer lis.Close()
    8.   s := grpc.NewServer()
    9.   service.RegisterChannelzServiceToServer(s)
    10.   go s.Serve(lis)
    11.   defer s.Stop()
    12.   /***** Initialize manual resolver and Dial *****/
    13.   r, rcleanup := manual.GenerateAndRegisterManualResolver()
    14.   defer rcleanup()
    15.   // Set up a connection to the server.
    16.   conn, err := grpc.Dial(r.Scheme()+":///test.server", grpc.WithInsecure(), grpc.WithBalancerName("round_robin"))
    17.   if err != nil {
    18.      log.Fatalf("did not connect: %v", err)
    19.   }
    20.   defer conn.Close()
    21.// ------省略不相关代码------
    

    主要代码说明:

    1.第13行,手动注册指定解析器

    1.2.4、xds 解析器(需要手动显示的注册)

    xds解析器,一般情况下,grpc服务器启动时并没有注册xds解析器,需要手动的注册。

    .../xds/internal/resolver/xds_resolver.go文件中初始化:

    func init() {
       resolver.Register(&xdsResolverBuilder{})
    }
    

    如何注册xds解析器呢?

    在.../xds/xds.go文件中:

    1.package xds
    2.import (
    3.   _ "google.golang.org/grpc/xds/internal/balancer" // Register the balancers.
    4.   _ "google.golang.org/grpc/xds/internal/resolver" // Register the xds_resolver
    5.)
    

    主要流程说明:

    1.第4行,注册xds解析器

    根据go语言的特性,如果某个包没有使用的话,是不会导入的;因此,如果我们想使用xds解析器的话,需要手动的显示导入,如下所示:

    随便找一个grpc服务器端启动的例子:

    import (
       "context"
       "fmt"
       "github.com/CodisLabs/codis/pkg/utils/log"
       "google.golang.org/grpc"
       "google.golang.org/grpc/codes"
       "google.golang.org/grpc/metadata"
       "google.golang.org/grpc/status"
       "net"
       pb "servergrpc/model/sgrpc/model2"
       "time"
       _ "google.golang.org/grpc/balancer/grpclb"
       //_ "google.golang.org/grpc/balancer/rls/internal"
       _ "google.golang.org/grpc/xds"
    )
    

    代码说明:

    1.第14行,将xds包导入到grpc服务器里,也就是将xds作为插件注册到了grpc服务器里

    1.2.5、自定义解析器

    在../examples/features/name_resolving/client/main.go文件进行初始化:

    func init() {
       // Register the example ResolverBuilder. This is usually done in a package's
       // init() function.
       resolver.Register(&exampleResolverBuilder{})
    }
    

    grpc-go源码自带的测试用例中还有其他自定义解析器,这里就不一一列举了。

    1.3、平衡构建器的注册

    平衡构建器的注册是通过../balancer/balancer.go文件中的Register函数来实现的。

    func Register(b Builder) {
       m[strings.ToLower(b.Name())] = b
    }
    

    注册时,需要传入一个构建器Builder,类型是接口:

    1.// Builder creates a balancer.
    2.type Builder interface {
    3.   // Build creates a new balancer with the ClientConn.
    4.   // 在 balancer_conn_wrappers.go文件中的newCCBalancerWrapper方法里,调用此方法了
    5.   Build(cc ClientConn, opts BuildOptions) Balancer
    6.   // Name returns the name of balancers built by this builder.
    7.   // It will be used to pick balancers (for example in service config).
    8.   Name() string
    9.}
    

    主要代码说明:

    1.第5行:为客户端连接器ClientConn创建一个平衡器

    2.第8行:返还的是平衡器的名称;注册或者根据名称获取指定平衡器时使用的。

    下面图片展示了当前grpc-go框架中内置的平衡构建器:

    注意:

    平衡构建器是用来创建平衡器的。

    平衡器的创建是在客户端跟服务器端进行链接过程中创建的。

    1.3.1、baseBuilder平衡构建器

    在grpc-go/balancer/base/balancer.go文件中定义了baseBuilder平衡构建器。

    1.type baseBuilder struct {
    2.   name          string
    3.   pickerBuilder PickerBuilder
    4.   config        Config
    }
    

    主要代码说明:

    1.第2行:用来设置构建器的名称

    2.第3行:这是一个picker构建器

    看一下PickerBuilder源码:

    在grpc-go/balancer/base/base.go文件中:

    1.// PickerBuilder creates balancer.Picker.
    2.type PickerBuilder interface {
    3.   // Build returns a picker that will be used by gRPC to pick a SubConn.
    4.   Build(info PickerBuildInfo) balancer.Picker
    }
    

    主要代码说明:

    1.第4行:创建一个balancer.Picker构建器;

    这个Picker到底是用来做什么呢?

    比方说,在某个场景下存在多个服务器端提供服务,客户端同时与这些服务器端建立起了链接,当客户端需要调用服务器端的服务时火端搜索源码v2.1,需要从这些链接中根据平衡器策略选择一个链接进行服务调用。(简单的说,就是picker需要从众多链接中选择一个进行帧的接收)

    下面图片显示了grpc-go框架中内置实现的PickerBuilder:

    baseBuilder构建器是基础构建器,启动时不需要注册;或者可以认为baseBuilder是一个平衡构建器模板,像下面的round_robin平衡构建器就是以这个模块为基础创建的。

    1.3.2、pickFirst平衡构建器注册

    在grpc-go/pickfirst.go文件中注册了pickFirst构建器:

    func init() {
       balancer.Register(newPickfirstBuilder())
    }
    

    其中,传入的构建器newPickerfirstBuilder(),代码如下:

    // PickFirstBalancerName is the name of the pick_first balancer.
    const PickFirstBalancerName = "pick_first"
    // 初始化Pickerfirst 构建器
    func newPickfirstBuilder() balancer.Builder {
       return &pickfirstBuilder{}
    }
    type pickfirstBuilder struct{}
    

    1.3.3、round_robin平衡器注册

    在grpc-go/balancer/roundrobin/roundrobin.go文件中:

    1.// Name is the name of round_robin balancer.
    2.const Name = "round_robin"
    3.// newBuilder creates a new roundrobin balancer builder.
    4.func newBuilder() balancer.Builder {
    5.   return base.NewBalancerBuilder(Name, &rrPickerBuilder{}, base.Config{HealthCheck: true})
    6.}
    7.func init() {
    8.   balancer.Register(newBuilder())
    9.}
    10.type rrPickerBuilder struct{}
    

    主要代码说明:

    1.第2行:设置平衡器的名称

    2.第4-6行:创建round_robin平衡构建器函数,内部调用的是base.NewBalancerBuilder函数

    3.第7-9行:将round_robin平衡器构建器注册到grpc服务器里

    4.第10行:定义一个rrPickerBuilder结构体;很明显,前缀rr是round_robin的首字母缩写;在grpc-go框架中,有很多类似的情况。

    接下来,看一下base.NewBalancerBuilder函数内部:

    1.func NewBalancerBuilder(name string, pb PickerBuilder, config Config) balancer.Builder {
    2.      return &baseBuilder{
    3.      name:          name,
    4.      pickerBuilder: pb,
    5.      config:        config,
    6.   }
    7.}
    

    主要代码说明:

    1.第2-6行:round_robin构建器内部调用的是baseBuilder构建器。

    在实现平衡器的过程中最主要的就是要实现PickerBuilder,如何众多链接中选择一个链接进行客户端跟服务器端的数据交换,也就是设置选择链接的策略。不同的平衡器选择策略不同,如随机选择,始终选择第一个pickerFirst,或者根据链接的权重进行选择,或者轮询选择策略round_robin。

    1.3.4、grpclb平衡器注册

    在grpc-go/balancer/grpclb/grpclb.go文件中,进行了初始化:

    1.func init() {
    2.   balancer.Register(newLBBuilder())
    3.   dns.EnableSRVLookups = true
    4.}
    5.// newLBBuilder creates a builder for grpclb.
    6.func newLBBuilder() balancer.Builder {
    7.      return newLBBuilderWithFallbackTimeout(defaultFallbackTimeout)
    8.}
    9.func newLBBuilderWithFallbackTimeout(fallbackTimeout time.Duration) balancer.Builder {
    10.   return &lbBuilder{
    11.      fallbackTimeout: fallbackTimeout,
    12.   }
    13.}
    14.type lbBuilder struct {
    15.   fallbackTimeout time.Duration
    16.}
    

    主要代码说明:

    1.第1-4行:注册lb构建器

    2.第6-8行:创建lb构建器函数,内部调用的是newLBBuilderWithFallbackTimeout函数

    需要说明的是,grpclb构建器在grpc启动时默认并没有启动,如果想要启动的话,需要显示的导入火端搜索源码v2.1,如下所示:

    在/grpc-go/examples/features/load_balancing/server/main.go测试用例中:

    1.package main
    2.import (
    3.   "context"
    4.   "fmt"
    5.   "github.com/CodisLabs/codis/pkg/utils/log"
    6.   "net"
    7.   "sync"
    8.   "google.golang.org/grpc"
    9.   _ "google.golang.org/grpc/balancer/grpclb"
    10.   pb "servergrpc/examples/features/proto/echo"
    11.)
    

    主要代码说明:

    1.第9行:将grpclb构建器显示的导入到服务器中

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论