• gRPC使用


    A high performance, open source universal RPC framework

    Why gRPC?

    gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.

     

    在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

     


    大致请求流程:

    1、客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。
    2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。
    3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。
    4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。
    5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。
    
    ❝ 一个RPC框架大致需要动态代理、序列化、网络请求、网络请求接受(netty实现)、动态加载、反射这些知识点。现在开源及各公司自己造的RPC框架层出不穷,唯有掌握原理是一劳永逸的。

    RPC框架是什么

    RPC 框架说白了就是让你可以像调用本地方法一样调用远程服务提供的方法,而不需要关心底层的通信细节。简单地说就让远程服务调用更加简单、透明。 RPC包含了客户端(Client)和服务端(Server)

    业界主流的 RPC 框架整体上分为三类:

    • 支持多语言的 RPC 框架,比较成熟的有 Google 的 gRPC、Apache(Facebook)的 Thrift;
    • 只支持特定语言的 RPC 框架,例如新浪微博的 Motan;
    • 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是 RPC 框架, 例如阿里的 Dubbo。

    gRPC的特性

    看官方文档的介绍,有以下几点特性:

    • grpc可以跨语言使用。支持多种语言 支持C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等编程语言
    • 基于 IDL ( 接口定义语言(Interface Define Language))文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
    • 通信协议基于标准的 HTTP/2 设计,支持·双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
    • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
    • 安装简单,扩展方便(用该框架每秒可达到百万个RPC)

    gRPC使用流程

    gprc的使用流程一般是这样的:

    1. 定义标准的proto文件(后面部分会详细讲解protobuf的使用)
    2. 生成标准代码
    3. 服务端使用生成的代码提供服务(参考各个语言的使用)
    4. 客户端使用生成的代码调用服务(参考各个语言的使用)

    官方教程:https://grpc.io/docs/languages/go/quickstart/

    分为3步:

    • 安装Go

    • 安装Protobuf编译器protoc: 用于编译.proto 文件

      • 步骤参考:grpc.io/docs/protoc…

      • 执行如下命令查看protoc的版本号,确认版本号是3+,用于支持protoc3

        protoc --version
        复制代码
    • 安装protoc编译器的Go语言插件

      • protoc-gen-go插件:用于生成xx.pb.go文件

        go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
         
      • protoc-gen-go-grpc插件:用于生成xx_grpc.pb.go文件

        go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
        复制代码

    注意:有的教程可能只让你安装protoc-gen-go,没有安装protoc-gen-go-grpc,那有2种情况:

    • 使用的是第1个版本github.com/golang/protobufprotoc-gen-go插件。
    • 使用的是第2个版本google.golang.org/protobufprotoc-gen-go插件并且protoc-gen-go版本号低于v1.20。从v1.20开始,第2个版本的protoc-gen-go插件不再支持生成gRPC服务定义。下面是官方说明:

    The v1.20 protoc-gen-go does not support generating gRPC service definitions. In the future, gRPC service generation will be supported by a new protoc-gen-go-grpc plugin provided by the Go gRPC project.

    The github.com/golang/protobuf version of protoc-gen-go continues to support gRPC and will continue to do so for the foreseeable future.

     

    安装protoc plugin for Go

    这个工具在go编译protoc文件的时候需要用到。

    $ go get -u github.com/golang/protobuf/protoc-gen-go

    安装完之后可执行程序protoc-gen-go会存放在$GOROOT/bin或者$GOPATH/bin下面。

    编写proto文件

    //声明proto的版本 只有 proto3 才支持 gRPC
    syntax = "proto3";
    // 将编译后文件输出在 github.com/lixd/grpc-go-example/helloworld/helloworld 目录
    option go_package = "github.com/lixd/grpc-go-example/helloworld/helloworld";
    // 指定当前proto文件属于helloworld包
    package helloworld;
    
    // 定义一个名叫 greeting 的服务
    service Greeter {
      // 该服务包含一个 SayHello 方法 HelloRequest、HelloReply分别为该方法的输入与输出
      rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    // 具体的参数定义
    message HelloRequest {
      string name = 1;
    }
    
    message HelloReply {
      string message = 1;
    }

    编译命令

    $ protoc --proto_path=IMPORT_PATH  --go_out=OUT_DIR  --go_opt=paths=source_relative path/to/file.proto
    

    这里简单介绍一下 golang 的编译姿势:

      • proto_path或者-I :指定 import 路径,可以指定多个参数,编译时按顺序查找,不指定时默认查找当前目录。
        • proto 文件中也可以引入其他 .proto 文件,这里主要用于指定被引入文件的位置。
    • go_out:golang编译支持,指定输出文件路径
    • go_opt:指定参数,比如--go_opt=paths=source_relative就是表明生成文件输出使用相对路径。
    • path/to/file.proto :被编译的 .proto 文件放在最后面

    上面通过proto定义的接口,没法直接在代码中使用,因此需要通过protoc编译器,将proto协议文件,编译成go语言代码。 在我们的demo中,按如下命令进行编译:

    # 切换到helloworld项目根目录,执行命令
    $ protoc -I proto/ --go_out=plugins=grpc:proto proto/helloworld.proto
    

    protoc命令参数说明:

    • -I 指定代码输出目录,忽略服务定义的包名,否则会根据包名创建目录
    • --go_out 指定代码输出目录,格式:--go_out=plugins=grpc:目录名
    • 命令最后面的参数是proto协议文件 编译成功后在proto目录生成了helloworld.pb.go文件,里面包含了,我们的服务和接口定义。

    protoc –go_out=plugins=grpc:. *.proto

     protoc.exe --go_out=plugins=grpc:. api.proto
     
     

    grpc引起错误

    proto文件中如果没有添加option go_package = "/proto";这行会报下面这种错误。

    protoc-gen-go: unable to determine Go import path for "proto/helloworld.proto"
    
    Please specify either:
            • a "go_package" option in the .proto source file, or
            • a "M" argument on the command line.
    
    See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.
    
    --go_out: protoc-gen-go: Plugin failed with status code 1.
    

    原因是protoc-gen-go的不同版本兼容性问题。

    解决办法:
    一是,在proto文件中加上option go_package = "/proto";
    二是采用老版本的proto-gen-go,使用命令切换为v1.3.2版本 go get -u github.com/golang/protobuf/protoc-gen-go@v1.3.2

    原文链接:https://blog.csdn.net/weixin_43851310/article/details/115431651

    我使用:

    option go_package = "./"; // 指定生成的go文件所在path

    protoc-gen-go: plugin are not supported;use ‘protoc --go-grpc_out=…’ to generate gRPC

     版本问题

    你还可能会遇到这种问题:

    --go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC
    

    这是因为你安装的是更新版本的protoc-gen-go,但是你却用了旧版本的生成命令。

    但是这两种方法都是可以完成目标的,只不过api不太一样。本文是基于Google版本的protoc-gen-go进行示范。

    使用下面命令成功:

    protoc --go_out=./service --go-grpc_out=./service pbfile\xxx.proto
    option go_package = "目标路径";
    
    如果想设置当前目录为包名则可以这样写:
    
    option go_package = "./;proto";
    
    其中proto是包名,可以自定义, ./表示当前目录。
    
    该选项主要是用于配置包依赖路径,例如 a.proto imports b.proto,则生成的pd.go文件也有依赖关系,因此要设置该路径。

    本demo项目结构如下:

    helloworld/
    ├── client.go - 客户端代码
    ├── go.mod  - go模块配置文件
    ├── proto     - 协议目录
    │   ├── helloworld.pb.go - rpc协议go版本代码
    │   └── helloworld.proto - rpc协议文件
    └── server.go  - rpc服务端代码
    

    初始化命令如下:

    # 创建项目目录
    mkdir helloworld
    # 切换到项目目录
    cd helloworld
    # 创建RPC协议目录
    mkdir proto
    # 初始化go模块配置,用来管理第三方依赖
    go mod init 

    server:
    package main
    
    import (
        "context"
        "log"
        "net"
    
        pb "helloworld/proto"
        "google.golang.org/grpc"
    )
    
    const (
        port = ":50051"
    )
    
    // greeterServer 定义一个结构体用于实现 .proto文件中定义的方法
    // 新版本 gRPC 要求必须嵌入 pb.UnimplementedGreeterServer 结构体
    type greeterServer struct {
        pb.UnimplementedGreeterServer
    }
    
    // SayHello 简单实现一下.proto文件中定义的 SayHello 方法
    func (g *greeterServer) 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() {
        listen, err := net.Listen("tcp", port)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        // 将服务描述(server)及其具体实现(greeterServer)注册到 gRPC 中去.
        // 内部使用的是一个 map 结构存储,类似 HTTP server。
        pb.RegisterGreeterServer(s, &greeterServer{})
        log.Println("Serving gRPC on 0.0.0.0" + port)
        if err := s.Serve(listen); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }

    具体步骤如下:

    • 1)定义一个结构体,必须包含pb.UnimplementedGreeterServer 对象;
    • 2)实现 .proto文件中定义的API;
    • 3)将服务描述及其具体实现注册到 gRPC 中;
    • 4)启动服务。
    package main
    
    import (
        "context"
        "log"
        "os"
        "time"
    
        pb "helloworld/proto"
        "google.golang.org/grpc"
    )
    
    const (
        address     = "localhost:50051"
        defaultName = "world"
    )
    
    func main() {
        conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
        if err != nil {
            log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewGreeterClient(conn)
    
        // 通过命令行参数指定 name
        name := defaultName
        if len(os.Args) > 1 {
            name = os.Args[1]
        }
        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())
    }

    具体步骤如下:

    • 1)首先使用 grpc.Dial() 与 gRPC 服务器建立连接;
    • 2)使用pb.NewGreeterClient(conn)获取客户端;
    • 3)通过客户端调用ServiceAPI方法client.SayHello
    https://ld246.com/article/1524816248447

  • 相关阅读:
    我的周记8——"因为相信,所以看见"
    我的周记7——“阳光开朗,自信表达一切”
    一些做设计挺不错的网站
    我的周记6——"不破楼兰誓不还“
    版本管理工具 Git
    我的周记5——"侵略如火,不动如山"
    SQLite-FMDatabase用法
    UIImage与Base64相互转换
    百度地图--地图标注的重复单击
    百度地图的单例模式
  • 原文地址:https://www.cnblogs.com/youxin/p/16728392.html
Copyright © 2020-2023  润新知