grpc客户端使用etcd提供的resolver

291 阅读2分钟

在new GRPC client的时候,可以传一个解析器resolver, 它主要用途是当有注册中心时候, 服务端启动多个实例进行注册, 客户端传一个服务名字, 解析器按照一定的方式(resolver要干的事)解析出一些ip,客户端通过负载均衡算法挑选出一个去链接

GRPC默认支持三种解析器, etcd sdk实现了grpc的resolver接口

image.png

根据grpc文档, 当client拨号时, 采用的target格式支持这几种. 上面说了用不同的拨号函数scheme默认值不同.新版本推荐用NewClient, Dial过期了.

grpc doc: custom-name-resolution

image.png

客户端

passthroughConn, err := grpc.NewClient(
        fmt.Sprintf("passthrough:///%s", backendAddr),            // Dial to "passthrough:///localhost:50051"
        grpc.WithTransportCredentials(insecure.NewCredentials()),
)

当用grpc.NewClient时, schema默认为dns, 当用grpc.Dial时, schema默认为passthrough

// withDefaultScheme is used to allow Dial to use "passthrough" as the default
// name resolver, while NewClient uses "dns" otherwise.
func withDefaultScheme(s string) DialOption {
    return newFuncDialOption(func(o *dialOptions) {
       o.defaultScheme = s
    })
}

etcd实现了grpc的resolver接口, client在连接etcd时候, scheme必须为etcd://

client, err := clientv3.NewFromURL(url)
etcdResolver, err := resolver.NewBuilder(client)
conn, err := grpc.Dial("etcd://localhost:2379/"+service.GetPathServerName("s1"),
        grpc.WithInsecure(),
        grpc.WithResolvers(etcdResolver),
        grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
        grpc.WithBlock(),
)

目录

├── client
│   └── main.go
├── go.mod
├── go.sum
├── main.go
├── proto
│   ├── helloworld.pb.go
│   ├── helloworld.proto
│   └── helloworld_grpc.pb.go
├── readme.txt
└── server
    └── main.go

代码

protp/helloworld.proto

syntax = "proto3";

option go_package=".;pb";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

//protoc --go_out=./proto --go-grpc_out=./proto ./proto/*.proto

server/main.go

server注册使用etcd sdk提供的grpcproxy, key ttl是60

// Package main implements a server for Greeter service.
package main

import (
    "context"
    "flag"
    "fmt"
    "go.uber.org/zap"
    pb "grpc-demo/proto"
    "log"
    "net"

    clientv3 "go.etcd.io/etcd/client/v3"
    "go.etcd.io/etcd/server/v3/proxy/grpcproxy"
    "google.golang.org/grpc"
)

var (
    port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    flag.Parse()
    config := clientv3.Config{
       Endpoints:   []string{"http://127.0.0.1:2379"},
       DialTimeout: 60,
    }
    etcdClient, err := clientv3.New(config)
    if err != nil {
       log.Fatal(err)
    }
    defer etcdClient.Close()
    lg, _ := zap.NewDevelopment()
    grpcproxy.Register(lg, etcdClient, "grpc-demo2/svc", fmt.Sprintf("127.0.0.1:%d", *port), 60)

    lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port))
    if err != nil {
       log.Fatalf("failed to listen: %v", err)
    }

    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
       log.Fatalf("failed to serve: %v", err)
    }
}

client/main.go

// Package main implements a client for Greeter service.
package main

import (
    "context"
    "flag"
    "fmt"
    clientv3 "go.etcd.io/etcd/client/v3"
    "go.etcd.io/etcd/client/v3/naming/resolver"
    pb "grpc-demo/proto"
    "log"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

const (
    defaultName = "world"
)

var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    name = flag.String("name", defaultName, "Name to greet")
)

func main() {
    flag.Parse()

    config := clientv3.Config{
       Endpoints:   []string{"http://127.0.0.1:2379"},
       DialTimeout: 60,
    }
    etcdClient, err := clientv3.New(config)
    if err != nil {
       log.Fatal(err)
    }
    defer etcdClient.Close()
    etcdResolver, err := resolver.NewBuilder(etcdClient)
    conn, err := grpc.Dial("etcd://localhost:2379/"+"grpc-demo2/svc",
       grpc.WithTransportCredentials(insecure.NewCredentials()),
       grpc.WithResolvers(etcdResolver),
       grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
       grpc.WithBlock(),
    )

    if err != nil {
       log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    for i := 0; i < 10; i++ {
       r, err := c.SayHello(ctx, &pb.HelloRequest{Name: fmt.Sprintf("%s %d", *name, i)})
       if err != nil {
          log.Fatalf("could not greet: %v", err)
       }
       log.Printf("Greeting: %s", r.GetMessage())
    }
}

测试

image.png
go run server/main.go --port 9001
go run server/main.go --port 9002
go run server/main.go --port 9003

go run client/main.go

image.png