gRPC之grpc resolver

发布时间:2023年12月24日

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;
}
$ protoc -I . --go_out=plugins=grpc:. ./helloword.proto

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)
	}
}
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.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.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
[root@zsx demo]# go run server/server2.go
2023/02/17 14:18:17 listen:  :50052
2023/02/17 14:18:32 Received: world
[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
# 项目结构
$ 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
文章来源:https://blog.csdn.net/qq_30614345/article/details/134494372
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。