• gRPC之grpc resolver



    title: gRPC之grpc resolver(二十)
    date: 2023-01-27
    top: 0
    categories:

    • Go
    • gRPC
      tags:
    • Go
    • gRPC
      description: |

    1、grpc resolver

    当我们的服务刚刚成型时,可能一个服务只有一台实例,这时候client要建立grpc连接很简单,只需要指定server

    的ip就可以了。但是,当服务成熟了,业务量大了,这个时候,一个实例就就不够用了,我们需要部署一个服务集

    群。一个集群有很多实例,且可以随时的扩容,部分实例出现了故障也没关系,这样就提升了服务的处理能力和稳

    定性,但是也带来一个问题,grpc的client,如何和这个集群里的server建立连接?

    这个问题可以一分为二,第一个问题:如何根据服务名称,返回实例的ip?这个问题有很多种解决方案,我们可以

    使用一些成熟的服务发现组件,例如consul或者zookeeper,也可以我们自己实现一个解析服务器;第二个问题,

    如何将我们选择的服务解析方式应用到grpc的连接建立中去?这个也不难,因为grpc的resolver,就是帮我们解决

    这个问题的,本篇,我们就来探讨一下,grpc的resolver是如何使用的。

    1.1 proto编写和编译

    syntax = "proto3";
    package helloworld;
    option go_package = "./;helloworld";
    
    service Greeter {
        rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    
    message HelloRequest {
        string name = 1;
    }
    
    message HelloReply {
        string message = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    $ protoc -I . --go_out=plugins=grpc:. ./helloword.proto
    
    • 1

    1.2 服务端编写

    这里需要编写两个服务端:

    package main
    
    import (
    	"context"
    	pb "demo/pb"
    	"google.golang.org/grpc"
    	"log"
    	"net"
    )
    
    const (
    	port = ":50051"
    )
    
    type server struct {
    	pb.UnimplementedGreeterServer
    }
    
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    	// 打印客户端传入HelloRequest请求的Name参数
    	log.Printf("Received: %v", in.GetName())
    	// 将name参数作为返回值,返回给客户端
    	return &pb.HelloReply{Message: "Service1: Hello " + in.GetName()}, nil
    }
    
    // main方法 函数开始执行的地方
    func main() {
    	// 调用标准库,监听50051端口的tcp连接
    	lis, err := net.Listen("tcp", port)
    	if err != nil {
    		log.Fatalf("failed to listen: %v", err)
    	}
    	log.Println("listen: ", port)
    	// 创建grpc服务
    	s := grpc.NewServer()
    	// 将server对象,也就是实现SayHello方法的对象,与grpc服务绑定
    	pb.RegisterGreeterServer(s, &server{})
    	// grpc服务开始接收访问50051端口的tcp连接数据
    	if err := s.Serve(lis); err != nil {
    		log.Fatalf("failed to serve: %v", err)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    package main
    
    import (
    	"context"
    	pb "demo/pb"
    	"google.golang.org/grpc"
    	"log"
    	"net"
    )
    
    const (
    	port2 = ":50052"
    )
    
    type server2 struct {
    	pb.UnimplementedGreeterServer
    }
    
    func (s *server2) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    	// 打印客户端传入HelloRequest请求的Name参数
    	log.Printf("Received: %v", in.GetName())
    	// 将name参数作为返回值,返回给客户端
    	return &pb.HelloReply{Message: "Service2: Hello " + in.GetName()}, nil
    }
    
    // main方法 函数开始执行的地方
    func main() {
    	// 调用标准库,监听50052端口的tcp连接
    	lis, err := net.Listen("tcp", port2)
    	if err != nil {
    		log.Fatalf("failed to listen: %v", err)
    	}
    	log.Println("listen: ",port2)
    	//创建grpc服务
    	s := grpc.NewServer()
    	//将server对象,也就是实现SayHello方法的对象,与grpc服务绑定
    	pb.RegisterGreeterServer(s, &server2{})
    	// grpc服务开始接收访问50052端口的tcp连接数据
    	if err := s.Serve(lis); err != nil {
    		log.Fatalf("failed to serve: %v", err)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1.3 客户端编写

    package main
    
    import (
    	"context"
    	pb "demo/pb"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/resolver"
    	"log"
    	"time"
    )
    
    // 全局注册Scheme为myservice的Resolver Build
    func init() {
    	resolver.Register(&myServiceBuilder{})
    }
    
    type myServiceBuilder struct {
    }
    
    func (*myServiceBuilder) Scheme() string {
    	return "myservice"
    }
    
    // 创建Resolver实例
    func (*myServiceBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
    	r := &myServiceResolver{
    		target: target,
    		cc:     cc,
    	}
    	r.start()
    	return r, nil
    }
    
    type myServiceResolver struct {
    	target resolver.Target
    	cc     resolver.ClientConn
    }
    
    // 根据target不同,解析出不同的端口
    func (r *myServiceResolver) start() {
    	if r.target.Endpoint() == "abc" {
    		r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ":50051"}}})
    	} else if r.target.Endpoint() == "efg" {
    		r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: ":50052"}}})
    	}
    }
    
    // 再次解析使用的解析方式不变
    func (r *myServiceResolver) ResolveNow(o resolver.ResolveNowOptions) {
    	r.start()
    }
    
    func (*myServiceResolver) Close() {}
    
    const (
    	address1 = "myservice:///abc"
    	address2 = "myservice:///efg"
    )
    
    func main() {
    	// myservice:///abc
    	// 访问服务端address,创建连接conn,地址格式myservice:///abc
    	conn, err := grpc.Dial(address1, grpc.WithInsecure(), grpc.WithBlock())
    	if err != nil {
    		log.Fatalf("did not connect: %v", err)
    	}
    	defer conn.Close()
    	c := pb.NewGreeterClient(conn)
    	// 设置客户端访问超时时间1秒
    	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    	defer cancel()
    	// 客户端调用服务端 SayHello 请求,传入Name 为 "world", 返回值为服务端返回参数
    	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
    	if err != nil {
    		log.Fatalf("could not greet: %v", err)
    	}
    	// 根据服务端处理逻辑,返回值也为"world"
    	log.Printf("Greeting: %s", r.GetMessage())
    	// myservice:///efg
    	conn2, err2 := grpc.Dial(address2, grpc.WithInsecure(), grpc.WithBlock())
    	if err2 != nil {
    		log.Fatalf("did not connect: %v", err)
    	}
    	defer conn2.Close()
    	c2 := pb.NewGreeterClient(conn2)
    	// 设置客户端访问超时时间1秒
    	ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second)
    	defer cancel2()
    	// 客户端调用服务端 SayHello 请求,传入Name 为 "world", 返回值为服务端返回参数
    	r2, err2 := c2.SayHello(ctx2, &pb.HelloRequest{Name: "world"})
    	if err2 != nil {
    		log.Fatalf("could not greet: %v", err2)
    	}
    	// 根据服务端处理逻辑,返回值也为"world"
    	log.Printf("Greeting: %s", r2.GetMessage())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    1.4 测试

    [root@zsx demo]# go run server/server1.go
    2023/02/17 14:18:06 listen:  :50051
    2023/02/17 14:18:32 Received: world
    
    • 1
    • 2
    • 3
    [root@zsx demo]# go run server/server2.go
    2023/02/17 14:18:17 listen:  :50052
    2023/02/17 14:18:32 Received: world
    
    • 1
    • 2
    • 3
    [root@zsx demo]# go run client/client.go
    2023/02/17 14:18:32 Greeting: Service1: Hello world
    2023/02/17 14:18:32 Greeting: Service2: Hello world
    
    • 1
    • 2
    • 3
    # 项目结构
    $ tree demo/
    demo/
    ├── client
    │   └── client.go
    ├── go.mod
    ├── go.sum
    ├── pb
    │   ├── helloword.pb.go
    │   └── helloword.proto
    └── server
        ├── server1.go
        └── server2.go
    
    3 directories, 7 files
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    【Redis】Redis锁详解
    信息系统项目管理师-采购管理论文提纲
    【IJCAI】CostFormer即插即用的MVS高效代价体聚合Transformer,FaceChain团队出品
    Verilog HDL——条件语句
    MybatisPlus 核心功能 条件构造器 自定义SQL Service接口 静态工具
    图论---最短路径问题
    Java日志系列——logback,log4j2使用
    Kafka - 图解生产者消息发送流程
    《nlp入门+实战:第九章:循环神经网络》
    鸿蒙原生应用元服务-访问控制(权限)开发应用权限列表三
  • 原文地址:https://blog.csdn.net/qq_30614345/article/details/134494372