远程过程调用系统gRPC

2022-09-21 10:24:35 浏览数 (1)

简介

  • gRPC 可以将 Protocol buffers 用作其接口定义语言 ( IDL ) 和底层消息交换格式(也可以使用其他的,例如json)
  • 远程调用,跨语言,更容易创建分布式应用和服务
  • 和其他RPC一样,基于服务定义的思想,结合Protocol buffers gRPC 插件,定义好服务后,服务端实现相应接口,客户端直接调用生成好的方法即可

主要使用场景

  • 低延迟、高度可扩展的分布式系统。
  • 开发与云服务器通信的移动客户端。
  • 设计一个需要准确、高效和语言独立的新协议。
  • 分层设计以实现扩展,例如。身份验证、负载平衡、日志记录和监控等。

核心概念

定义服务

代码语言:javascript复制
service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}
四种方式
  1. 简单rpc,就像正常调用函数一样 rpc GetFeature(Point) returns (Feature) {}
流式rpc

解决的问题

  • 传输的数据太大,造成瞬时网络压力
  • 大数据需要接收完才能处理

分类

  1. 服务端流式rpc rpc ListFeatures(Rectangle) returns (stream Feature) {}
  2. 客户端流式rpc rpc RecordRoute(stream Point) returns (RouteSummary) {}
  3. 双向流式rpc rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    • 服务端收到请求后,不用等到读取完才响应,可以接收一点,响应一点,官网的原话是:服务器和客户端可以玩“乒乓”

具体介绍看官方文档,现在没用到这块

同步和异步

在 gRPC-Go 中,RPC 以阻塞/同步模式运行,这意味着 RPC 调用等待服务器响应,并且将返回响应或错误。

看来只能自己起goroutine了,但这样很麻烦,野生goroutine需要自己去维护异常处理和日志

设置超时

可主动终止RPC

示例

定义服务helloworld.proto

代码语言:javascript复制
syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

利用buf生成代码

执行buf mod init生成buf.yaml

配置buf,buf.gen.yaml

代码语言:javascript复制
# 配置protoc生成规则
version: v1
managed:
 enabled: true
 go_package_prefix:
 # proto文件中不使用option定义包名称,因为proto生成代码可以放在不同项目中使用,因此在buf.gen.yaml中进行定义
   default: helloworld
   except:
     - buf.build/googleapis/googleapis
plugins:
 # 使用go插件生成go代码
 - name: go
   out: ./
   opt: paths=source_relative
 # 使用go-grpc插件生成grpc代码
 - name: go-grpc
   out: ./
   opt:
     - paths=source_relative
     - require_unimplemented_servers=false

执行buf generate 生成helloworld.pb.gohelloworld_grpc.pb.go

创建服务端

代码语言:javascript复制
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
}
  • 继承生成的默认实现类
  • 重写实现接口方法

启动服务端

代码语言:javascript复制
    flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf(":%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)
    }
  • 监听端口
  • 创建 gRPC 服务器的实例
  • 向 gRPC 服务器注册我们的服务实现
  • 调用Serve()服务器以进行阻塞等待,直到进程被杀死或被Stop()调用

创建客户端

代码语言:javascript复制
    flag.Parse()
    // Set up a connection to the server.
    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
    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()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
  • 创建 gRPC通道来与服务器通信
  • 可以使用DialOptions在服务需要时设置身份验证凭据(例如,TLS、GCE 凭据或 JWT 凭据)
  • 创建客户端
  • 调用服务方法

0 人点赞