gRPC

954 阅读10分钟

本地调用过程

  • 1、 返回地址入栈
  • 2、参数入栈
  • 3、提升堆栈空间
  • 4、函数参数的复制
  • 5、执行函数调用
  • 6、清空堆栈

rpc调用过程

  • 1.client 以本地的方式进行的调用服务
  • 2.client stub接收到调用后负责将方法 参数等组装成能够进行网络传输的消息体
  • 3.cleint stub找到服务端的地址 并将消息发送给server stub
  • 4.server stub收到消息后进行解码
  • 5.server stub根据解码结果调用本地的服务
  • 6.本地服务执行结果并将结果返回后给server stub
  • 7.server stub将返回结果打包成消息并发送至消费方
  • 8.client stub接受到消息
  • 9.client得到返回结果

stub存根:为屏蔽客户调用远程主机上的对象,必须提供某种方式来模拟本地对象,这种本地对象称为存根(stub),存根负责接收本地方法调用,并将它们委派给各自的具体实现对象。

客户端存根:存放服务端的地址、端口信息。将客户端的请求参数打包成网络信息,发送到服务方。接收服务方返回的数据包。该段程序运行在客户端。

服务端存根:接收客户端发送的数据包,解析数据包,调用具体的服务方法。将调用结果打包发送给客户端一方。该段程序运行在服务端。

为什么需要rpc?

两台机器 一台机器想调用另一台机器的函数执行某个功能 由于是两个不同的进程 ,所以 无法使用函数指针来调用该函数而只能通过网络请求来调用的具体函数 (两台机器之间可以各自维护一个关联式容器 从而找到要调用的函数). RPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

grpc简介

grpc是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链接功能,从而有助于节省带宽、降低的TCP链接次数、节省CPU使用。

grpc的特点

  • 1、语言中立,支持多种语言;
  • 2、基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
  • 3、通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
  • 4、序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。

grpc相关

  • proto buffer

    • 什么是 proto buffer

      proto buffer是Google使用的一个开源软件 ,数据打包小 传输数据快一种跨语>言和跨平台的数据序列化协议, 自带编译器,定义proto源文件,可编译成多种语言的代>码。

    • 与XML和Json相比

      与XML/JSON相比,序列化效率更快、体积更小、更安全 与XML/JSON相比,可读性差、灵活性较低

  • HTTP2

    • http2与http1相比

    1、HTTP/2 传输的数据是二进制的。相比 HTTP/1.1 的纯文本数据,二进制数据一个显而易见的好处是:更小的传输体积。这就意味着更低的负载。二进制的帧也更易于解析而且不易出错,纯文本帧在解析的时候还要考虑处理空格、大小写、空行和换行等问题,而二进制帧就不存在这个问题。

    2、在 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」。 HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。 因此 HTTP/2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。

grpc的优缺点

  • 优点

1、protobuf二进制消息,性能好/效率高(空间和时间效率都很不错) 2、proto文件生成目标代码,简单易用 3、序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML,JSON都是这种方式) 4、支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级 支持多种语言(可以把proto文件看做IDL文件)

  • 缺点

1、GRPC尚未提供连接池,需要自行实现 2、尚未提供“服务发现”、“负载均衡”机制 3、因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持) 4、 Protobuf二进制可读性比较差

grpc的应用场景

  • 低延迟、高扩展性、分布式的系统
  • 同云服务器进行通信的移动应用客户端
  • 设计语言独立、高效、精确的新协议
  • 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等

四种流行RPC框架对比

对比Grpc和Thrift

选择grpc
  • 需要良好的文档、示例
  • 喜欢、习惯HTTP/2、ProtoBuf
  • 对网络传输带宽敏感
选择grpc
  • 需要在非常多的语言间进行数据交换
  • 对CPU敏感
  • 协议层、传输层有多种控制要求
  • 需要稳定的版本
  • 不需要良好的文档和示例

grpc的简单使用

写一个简单的订单信息查询

1、定义服务

想要实现的是通过grpc框架进行远程服务调用,首先第一步要有服务。gRPC框架默认使用protocol buffers作为接口定义语言,用于描述网络传输消息结构。除此之外,还可以使用protobuf定义服务接口。

  • 编写 order.proto
syntax = "proto3";
package message;

//订单参数
message OrderRequest{
    string orderId = 1;
    int64 timeStamp = 2;
}

//订单信息
message OrderInfo{
    string OrderId = 1;
    string OrderName = 2;
    string OrderStatus = 3;
}

//订单服务service定义
service OrderService{
    rpc GetOrderInfo(OrderRequest) returns(OrderInfo);
}

通过proto文件定义了数据结构的同时,还定义了要实现的服务接口GetOrderInfo() 即是具体服务接口的定义,在SayHello 接口定义中,OrderRequest表示的是请求传递的参数,OrderInfo表示处理结果返回数据参数。

定义 rpc 方法,指定请求的和响应类型。gRPC 允许你定义4种类型的 service 方法,这些都在可以在OrderService服务中使用:
  • 一个 简单 RPC , 客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样。
rpc GetFeature(Point) returns (Feature) {}
  • 一个 服务器端流式 RPC , 客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。从例子中可以看出,通过在 响应 类型前插入 stream 关键字,可以指定一个服务器端的流方法。
rpc ListFeatures(Rectangle) returns (stream Feature) {}
  • 一个 客户端流式 RPC , 客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。通过在 请求 类型前指定 stream 关键字来指定一个客户端的流方法。
rpc RecordRoute(stream Point) returns (RouteSummary) {}
  • 一个 双向流式 RPC 是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。 每个流中的消息顺序被预留。你可以通过在请求和响应前加 stream 关键字去制定方法的类型。
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
2、编译 .proto文件

注意:要有下面两个文件才能使用:protoc --go_out=plugins=grpc:. .proto  命令
(1)protoc.exe下载: github.com/google/prot…       把解压后的 protoc.exe 放入到 GOPATH\bin 中。 (2)protoc-gen-go.exe下载:github.com/golang/prot… 进入解压后的protobuf-master文件夹,进入protoc-gen-go文件夹运行 go build命令, 将生成的 protoc-gen-go可执行文件,放在GOPATH\bin目录下。

3、服务端接口实现

在 .proto 定义好服务接口并生成对应的go语言文件后,需要对服务接口做具体的实现。定义服务接口具体由OrderServiceImpl进行实现,并实现GetOrderInfo详细内容,服务实现逻辑与前文所述内容相同。不同点是服务接口参数的变化。

  • 编写server.go
type OrderServiceImpl struct {
}

//具体的方法实现
func (os *OrderServiceImpl)GetOrderInfo(ctx context.Context, request *message.OrderRequest) (*message.OrderInfo, error){
   orderMap := map[string]message.OrderInfo{
      "201907300001":{OrderId:"201907300001", OrderName:"衣服",OrderStatus:"已付款"},
      "201907310001":{OrderId:"201907310001", OrderName:"零食",OrderStatus:"已付款"},
      "201907310002":{OrderId:"201907310002", OrderName:"食品",OrderStatus:"未付款"},
   }

   var respones *message.OrderInfo
   current := time.Now().Unix()
   if request.TimeStamp > current{
      *respones = message.OrderInfo{OrderId: "0", OrderName: "", OrderStatus: "订单信息异常"}
   }else {
      result := orderMap[request.OrderId]
      if request.OrderId != ""{
         fmt.Println(result)
         return &result,nil
      }else {
         return nil, errors.New("server error")
      }
   }
   return respones, nil
}

func main() {
   server := grpc.NewServer()    //创建一个server对象
   message.RegisterOrderServiceServer(server, new(OrderServiceImpl))  //将服务注册到grpc框架当中
   lis, err := net.Listen("tcp","localhost:8090") //监听端口
   if err != nil{
      panic(err.Error())
   }
   server.Serve(lis)
}
4、客户端接口实现

客户端去调用GetOrderInfo接口 进行服务端的访问。

  • 编写client.go
func main() {
   //1、Dial连接
   conn, err := grpc.Dial("localhost:8090",grpc.WithInsecure())
   if err != nil{
      panic(err.Error())
   }
   defer conn.Close()

   //2、创建客户端对象
   orderServiceClient := message.NewOrderServiceClient(conn)

   orderRequest := &message.OrderRequest{OrderId:"201907300001",TimeStamp: time.Now().Unix()}
   orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(),orderRequest)

   if orderInfo != nil{
      fmt.Println(orderInfo.OrderId)
      fmt.Println(orderInfo.OrderName)
      fmt.Println(orderInfo.OrderStatus)
   }
}
  • 服务端运行结果:
  • 客户端运行结果: