代码改变世界

gRPC使用

2022-09-25 17:58  youxin  阅读(397)  评论(0编辑  收藏  举报

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