RPC

首先回顾下计算机网络的五(七)层协议:物理层、数据链路层、网络层、传输层、(会话层、表示层)和应用层。那么从协议上来讲:

  • TCP是传输层协议,主要解决数据如何在网络中传输
  • HTTP 是应用层协议,主要解决如何包装数据(文本信息),是建立在tcp协议之上的应用。TCP协议是以二进制数据流的形式解决传输层的事儿,但对上层的应用开发极不友好,所以面向应用层的开发又产生了HTTP协议。

而socket 是针对TCP或UDP的具体接口实现,提供了在传输层进行网络编程的方法。

什么是RPC?

RPC(Remote Procedure Call)是远程过程调用,比如说现在有两台服务器A, B,一个在A服务器上的应用想要调用B服务器上的应用提供的某个,由于不在两个方法不在一个内存空间,不能直接调用,需要通过网络表达调用的语义和传达调用的数据。常存在于分布式系统中。

为何有http协议之后,还要RPC调用?

RPC跟HTTP不是对立面,RPC中可以使用HTTP作为通讯协议。RPC是一种设计、实现框架,通讯协议只是其中一部分。 RPC的本质是提供了一种轻量无感知的跨进程通信的方式,在分布式机器上调用其他方法与本地调用无异(远程调用的过程是透明的,你并不知道这个调用的方法是部署在哪里,通过PRC能够解耦服务)。RPC是根据语言的API来定义的,而不是基于网络的应用来定义的,调用更方便,协议私密更安全、内容更小效率更高。 http接口是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;优点就是简单、直接、开发方便。利用现成的http协议 进行传输。但是如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先(基于TCP协议的情况下)就是长链接,不必每次通信都要像http 一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统 一化的操作。第三个来说就是安全性。最后就是最近流行的服务化架构、服务化治理,RPC框架是一个强力的支撑。

RPC 中要解决的问题:

  • 建立通信:在客户端与服务端建立起数据传输通道,大都是TCP连接(gRPC使用了HTTP2)。
  • 寻址:A服务器上的应用需要告诉RPC框架:B服务器地址、端口,调用函数名称。所以必须实现待调用方法到call ID的映射。
  • 序列化与反序列化:由于网络协议都是二进制的,所以调用方法的参数在进行传递时首先要序列化成二进制,B服务器收到请求后要再对参数进行反序列化。恢复为内存中的表达方式,找到对应的方法进行本地调用,得到返回值。返回值从B到A的传输仍要经过序列化与反序列化的过程。

    常见名词小结

    | 名词 | 特点 | | :—-: | —- | | RPC | 远程过程调用(分布式、微服务间的方法调用) | | HTTP | 无状态,每次请求都要发送一个request,服务器响应之后就断掉(http header中的keep-alive指的是tcp) | | TCP | 面向连接,三次握手保证通信可靠 | | UDP | 非面向连接,不可靠,速度快(可以手动对数据收发进行验证,IM系统多采用,QQ) | | socket | TCP协议的接口实现,面向传输层进行网络编程 |

既然有 HTTP 请求,为什么还要用 RPC 调用?

HTTP/2介绍

新特性:

  • 新的二进制格式
    HTTP1.X都是基于文本解析,而因为文本表现形式的多样性,基于文本协议的格式解析天然存在健壮性问题。而采用二进制格式后实现方便且健壮。
  • 多路复用
    多个request共享一个连接。
  • header压缩
    在HTTP1.x中header信息很多,且每次都会重复发送,造成很大浪费。HTTP2.0使用encoder减少了传输的header大小,且通信双方都缓存一份包含了header信息的表,此后的请求可以只发送差异数据,避免信息的重复传输,进一步减少需要传输的内容大小。
  • 服务端推送
    主要的思想是:当一个客户端请求资源X,而服务器知道它很可能也需要资源Z的情况下,服务器可以在客户端发送请求前,主动将资源Z推送给客户端。这个功能帮助客户端将Z放进缓存以备将来之需。也遵守同源策略,且客户端可以拒绝推送过来的资源。

HTTP/2 相比 1.0 有哪些重大改进?
HTTP/2协议–特性扫盲篇
HTTP,HTTP2.0,SPDY,HTTPS 你应该知道的一些事

gRPC

gRPC是谷歌开源的一个 RPC 框架,面向移动和 HTTP/2 设计。

  • 内容交换格式采用ProtoBuf(Google Protocol Buffers),开源已久,提供了一种灵活、高效、自动序列化结构数据的机制,作用与XML,Json类似,但使用二进制,(反)序列化速度快,压缩效率高。
  • 传输协议 采用http2,性能比http1.1好了很多

和很多RPC系统一样,服务端负责实现定义好的接口并处理客户端的请求,客户端根据接口描述直接调用需要的服务。客户端和服务端可以分别使用gPRC支持的不同语言实现。
ProtoBuf 具有强大的IDL(interface description language,接口描述语言)和相关工具集(主要是protoc)。用户写好.proto描述文件后,protoc可以将其编译成众多语言的接口代码。

gRPC 简介:

gRPC 是一款高性能、开源的 RPC 框架,产自 Google,基于 ProtoBuf 序列化协议进行开发,支持多种语言(Golang、Python、Java等),本篇只介绍 Python 的 gRPC 使用。因为 gRPC 对 HTTP/2 协议的支持使其在 Android、IOS 等客户端后端服务的开发领域具有良好的前景。gRPC 提供了一种简单的方法来定义服务,同时客户端可以充分利用 HTTP/2 stream 的特性,从而有助于节省带宽、降低 TCP 的连接次数、节省CPU的使用等。

安装:

  1. gRPC 的安装:

    1. pip install grpcio
  2. 安装 ProtoBuf 相关的 python 依赖库:

    1. pip install protobuf
  3. 安装 python grpc 的 protobuf 编译工具:

    1. pip install grpcio-tools

实践:

下面我们使用 gRPC 定义一个接口,该接口实现对传入的数据进行大写的格式化处理。

  • 创建项目 python demo 工程:

    Python微服务开发(一):gRPC - 图1
  1. client目录下的 main.py 实现了客户端用于发送数据并打印接收到 server 端处理后的数据。
  2. server 目录下的 main.py 实现了 server 端用于接收客户端发送的数据,并对数据进行大写处理后返回给客户端。
  3. example 包用于编写 proto 文件并生成 data 接口。
  • 定义 gRPC 接口:
  1. syntax = "proto3";
  2. package example;
  3. service FormatData {
  4. rpc DoFormat(Data) returns (Data){}
  5. }
  6. message Data {
  7. string text = 1;
  8. }
  • 编译 protobuf:

    python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./data.proto   
    #在 example 目录中执行编译,会生成:data_pb2.py 与 data_pb2_grpc.py
    
  • 实现 server 端:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
import grpc
import time
from concurrent import futures
from example import data_pb2, data_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
_HOST = 'localhost'
_PORT = '8080'
class FormatData(data_pb2_grpc.FormatDataServicer):
    def DoFormat(self, request, context):
        str = request.text
        return data_pb2.Data(text=str.upper())
def serve():
    grpcServer = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
    data_pb2_grpc.add_FormatDataServicer_to_server(FormatData(), grpcServer)
    grpcServer.add_insecure_port(_HOST + ':' + _PORT)
    grpcServer.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        grpcServer.stop(0)
if __name__ == '__main__':
    serve()
  • 实现 client 端:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import grpc
from example import data_pb2, data_pb2_grpc
_HOST = 'localhost'
_PORT = '8080'
def run():
    conn = grpc.insecure_channel(_HOST + ':' + _PORT)
    client = data_pb2_grpc.FormatDataStub(channel=conn)
    response = client.DoFormat(data_pb2.Data(text='hello,world!'))
    print("received: " + response.text)
if __name__ == '__main__':
    run()
  • 执行验证结果:

    先启动 server,之后再执行 client

client 侧控制台如果打印的结果为:“received: HELLO,WORLD!” ,证明 gRPC 接口定义成功。


  • grpc 的基础: protobuf
  • grpc helloworld: python 实战 grpc 环境配置
  • grpc basic: grpc 4 种通信方式

    grpc 的基础: protobuf

    grpc 使用 protobuf 进行数据传输. protobuf 是一种数据交换格式, 由三部分组成:

  • proto 文件: 使用的 proto 语法的文本文件, 用来定义数据格式

proto语法现在有 proto2 和 proto3 两个版本, 推荐使用 proto3, 更加简洁明了

// [python quickstart](https://grpc.io/docs/quickstart/python.html#run-a-grpc-application)
// python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
// helloworld.proto
syntax = "proto3";
service Greeter {
    rpc SayHello(HelloRequest) returns (HelloReply) {}
    rpc SayHelloAgain(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
    string name = 1;
}
message HelloReply {
    string message = 1;
}
  • protoc: protobuf 编译器(compile), 将 proto 文件编译成不同语言的实现, 这样不同语言中的数据就可以和 protobuf 格式的数据进行交互
  • protobuf 运行时(runtime): protobuf 运行时所需要的库, 和 protoc 编译生成的代码进行交互

使用 protobuf 的过程:
编写 proto 文件 -> 使用 protoc 编译 -> 添加 protobuf 运行时 -> 项目中集成
更新 protobuf 的过程:
修改 proto 文件 -> 使用 protoc 重新编译 -> 项目中修改集成的地方

PS: proto3 的语法非常非常的简单, 上手 protobuf 也很轻松, 反而是配置 protoc 的环境容易卡住, 所以推荐使用 python 入门, 配置 protoc 这一步非常省心.

grpc helloworld: python 实战 grpc 环境配置

上面已经定义好了 grpc helloworld demo 所需的 proto 文件, 现在来具体看看 python 怎么一步步把 grpc helloworld 的环境搭建起来:

  • protobuf 运行时(runtime)

这一步很简单, 安装 grpc 相关的 python 模块(module) 即可

pip install grpcio
  • 使用 protoc 编译 proto 文件, 生成 python 语言的实现
    # 安装 python 下的 protoc 编译器
    pip install grpcio-tools
    # 编译 proto 文件
    python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
    python -m grpc_tools.protoc: python 下的 protoc 编译器通过 python 模块(module) 实现, 所以说这一步非常省心
    --python_out=. : 编译生成处理 protobuf 相关的代码的路径, 这里生成到当前目录
    --grpc_python_out=. : 编译生成处理 grpc 相关的代码的路径, 这里生成到当前目录
    -I. helloworld.proto : proto 文件的路径, 这里的 proto 文件在当前目录
    
    编译后生成的代码:
  1. helloworld_pb2.py: 用来和 protobuf 数据进行交互
  2. helloworld_pb2_grpc.py: 用来和 grpc 进行交互
  • 最后一步, 编写 helloworld 的 grpc 实现:

服务器端: helloworld_grpc_server.py

from concurrent import futures
import time
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
# 实现 proto 文件中定义的 GreeterServicer
class Greeter(helloworld_pb2_grpc.GreeterServicer):
    # 实现 proto 文件中定义的 rpc 调用
    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message = 'hello {msg}'.format(msg = request.name))
    def SayHelloAgain(self, request, context):
        return helloworld_pb2.HelloReply(message='hello {msg}'.format(msg = request.name))
def serve():
    # 启动 rpc 服务
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    try:
        while True:
            time.sleep(60*60*24) # one day in seconds
    except KeyboardInterrupt:
        server.stop(0)
if __name__ == '__main__':
    serve()

客户端: helloworld_grpc_client.py

import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
    # 连接 rpc 服务器
    channel = grpc.insecure_channel('localhost:50051')
    # 调用 rpc 服务
    stub = helloworld_pb2_grpc.GreeterStub(channel)
    response = stub.SayHello(helloworld_pb2.HelloRequest(name='czl'))
    print("Greeter client received: " + response.message)
    response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='daydaygo'))
    print("Greeter client received: " + response.message)
if __name__ == '__main__':
    run()

运行 python helloworld_grpc_server.pypython helloworld_grpc_client.py, 就可以看到效果了

grpc basic: 4 种通信方式

helloworld 使用了最简单的 grpc 通信方式: 类似 http 协议的一次 request+response.
根据不同的业务场景, grpc 支持 4 种通信方式:

  • 客服端一次请求, 服务器一次应答
  • 客服端一次请求, 服务器多次应答(流式)
  • 客服端多次请求(流式), 服务器一次应答
  • 客服端多次请求(流式), 服务器多次应答(流式)

官方提供了一个 route guide service 的 demo, 应用到了这 4 种通信方式, 具体的业务如下:

  • 数据源: json 格式的数据源, 存储了很多地点, 每个地点由经纬度(point)和地名(location)组成
  • 通信方式 1: 客户端请求一个地点是否在数据源中
  • 通信方式 2: 客户端指定一个矩形范围(矩形的对角点坐标), 服务器返回这个范围内的地点信息
  • 通信方式 3: 客户端给服务器发送多个地点信息, 服务器返回汇总信息(summary)
  • 通信方式 4: 客户端和服务器使用地点信息 聊天(chat)

对应的 proto 文件: routeguide.proto:

// [python quickstart](https://grpc.io/docs/quickstart/python.html#run-a-grpc-application)
// python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. routeguide.proto
syntax = "proto3";
service RouteGuide {
    // simple rpc
    rpc GetFeature(Point) returns (Feature) {}
    // server2client stream rpc
    rpc ListFeature(Rectangle) returns (stream Feature) {}
    // client2server stream rpc
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    // stream rpc
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
message Point {
    int32 latitude = 1;
    int32 longitude = 2;
}
message Rectangle {
    Point lo = 1;
    Point hi = 2;
}
message Feature {
    string name = 1;
    Point location = 2;
}
message RouteNote {
    Point location = 1;
    string message = 2;
}
message RouteSummary {
    int32 point_count = 1;
    int32 feature_count = 2;
    int32 distance = 3;
    int32 elapsed_time = 4;
}

proto 中想要表示流式传输, 只需要添加 stream 关键字即可
同样的, 使用 protoc 生成代码:

python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. routeguide.proto

生成了 routeguide_pb2.py routeguide_pb2_grpc.py 文件, 和上面的 helloworld 对应
这里需要增加一个 routeguide_db.py, 用来处理 demo 中数据源(routeguide_db.json)文件:

import json
import routeguide_pb2
def read_routeguide_db():
    feature_list = []
    with open('routeguide_db.json') as f:
        for item in json.load(f):
            feature = routeguide_pb2.Feature(
                name = item['name'],
                location = routeguide_pb2.Point(
                    latitude=item['location']['latitude'],
                    longitude=item['location']['longitude']
                )
            )
            feature_list.append(feature)
    return feature_list

处理 json 的过程很简单, 解析 json 数据得到由坐标点组成的数组
好了, 还剩下一个难题: 怎么处理流式数据呢?. 答案是 for-in + yield

  • 客户端读取服务器发送的流式数据

    print("-------------- ListFeatures --------------")
    response = stub.ListFeature(routeguide_pb2.Rectangle(
      lo = routeguide_pb2.Point(latitude=400000000, longitude=-750000000),
      hi=routeguide_pb2.Point(latitude=420000000, longitude=-730000000)
    ))
    for feature in response:
      print("Feature called {name} at {location}".format(name=feature.name, location=feature.location))
    
  • 客户端发送流式数据给服务器

    def generate_route(feature_list):
      for _ in range(0, 20):
          random_feature = feature_list[random.randint(0, len(feature_list) - 1)]
          print("random feature {name} at {location}".format(
              name=random_feature.name, location=random_feature.location))
          yield random_feature.location
    print("-------------- RecordRoute --------------")
    feature_list = routeguide_db.read_routeguide_db()
    route_iterator = generate_route(feature_list)
    response = stub.RecordRoute(route_iterator)
    print("point count: {point_count} feature count: {feature_count} distance: {distance} elapsed time:{elapsed_time}".format(
      point_count  = response.point_count,
      feature_count = response.feature_count,
      distance = response.distance,
      elapsed_time = response.elapsed_time
    ))
    
  • 完整的服务器端代码: routeguide_grpc_server.py:

    from concurrent import futures
    import math
    import time
    import grpc
    import routeguide_pb2
    import routeguide_pb2_grpc
    import routeguide_db
    def get_feature(db, point):
      for feature in db:
          if feature.location == point:
              return feature
      return None
    def get_distance(start, end):
      """Distance between two points."""
      coord_factor = 10000000.0
      lat_1 = start.latitude / coord_factor
      lat_2 = end.latitude / coord_factor
      lon_1 = start.longitude / coord_factor
      lon_2 = end.longitude / coord_factor
      lat_rad_1 = math.radians(lat_1)
      lat_rad_2 = math.radians(lat_2)
      delta_lat_rad = math.radians(lat_2 - lat_1)
      delta_lon_rad = math.radians(lon_2 - lon_1)
      # Formula is based on http://mathforum.org/library/drmath/view/51879.html
      a = (pow(math.sin(delta_lat_rad / 2), 2) +
           (math.cos(lat_rad_1) * math.cos(lat_rad_2) * pow(
               math.sin(delta_lon_rad / 2), 2)))
      c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
      R = 6371000
      # metres
      return R * c
    class RouteGuide(routeguide_pb2_grpc.RouteGuideServicer):
      def __init__(self):
          self.db = routeguide_db.read_routeguide_db()
      def GetFeature(self, request, context):
          feature = get_feature(self.db, request)
          if feature is None:
              return routeguide_pb2.Feature(name = '', location = request)
          else:
              return feature
      def ListFeature(self, request, context):
          left = min(request.lo.longitude, request.hi.longitude)
          right = max(request.lo.longitude, request.hi.longitude)
          top = max(request.lo.latitude, request.hi.latitude)
          bottom = min(request.lo.latitude, request.hi.latitude)
          for feature in self.db:
              if (feature.location.longitude >= left
                  and feature.location.longitude <= right
              and feature.location.latitude >= bottom
              and feature.location.latitude <= top):
                  yield feature
      def RecordRoute(self, request_iterator, context):
          point_count = 0
          feature_count = 1
          distance = 0.0
          prev_point = None
          start_time = time.time()
          for point in request_iterator:
              point_count += 1
              if get_feature(self.db, point):
                  feature_count += 1
              if prev_point:
                  distance += get_distance(prev_point, point)
              prev_point = point
          elapsed_time = time.time() - start_time
          return routeguide_pb2.RouteSummary(
              point_count = point_count,
              feature_count = feature_count,
              distance = int(distance),
              elapsed_time = int(elapsed_time)
          )
      def RouteChat(self, request_iterator, context):
          prev_notes = []
          for new_note in request_iterator:
              for prev_note in prev_notes:
                  if prev_note.location == new_note.location:
                      yield prev_note
              prev_notes.append(new_note)
    def serve():
      server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
      routeguide_pb2_grpc.add_RouteGuideServicer_to_server(RouteGuide(), server)
      server.add_insecure_port('[::]:50051')
      server.start()
      try:
          while True:
              time.sleep(60*60*24) # one day in seconds
      except KeyboardInterrupt:
          server.stop(0)
    if __name__ == '__main__':
      serve()
    
  • 完整的客户端代码: routeguide_grpc_client.py:

    import grpc
    import routeguide_pb2
    import routeguide_pb2_grpc
    import routeguide_db
    import random
    def get_feature(feature):
      if not feature.location:
          print("Server returned incomplete feature")
          return
      if feature.name:
          print("Feature called {name} at {location}".format(name = feature.name, location = feature.location))
      else:
          print("Found no feature at {location}".format(location = feature.location))
    def generate_route(feature_list):
      for _ in range(0, 20):
          random_feature = feature_list[random.randint(0, len(feature_list) - 1)]
          print("random feature {name} at {location}".format(
              name=random_feature.name, location=random_feature.location))
          yield random_feature.location
    def make_route_note(message, latitude, longitude):
      return routeguide_pb2.RouteNote(
          message=message,
          location=routeguide_pb2.Point(latitude=latitude, longitude=longitude))
    def generate_route_note():
      msgs = [
          make_route_note('msg 1', 0, 0),
          make_route_note('msg 2', 1, 0),
          make_route_note('msg 3', 0, 1),
          make_route_note('msg 4', 0, 0),
          make_route_note('msg 5', 1, 1),
      ]
      for msg in msgs:
          print("send message {message} location {location}".format(message = msg.message, location = msg.location))
          yield msg
    def run():
      channel = grpc.insecure_channel('localhost:50051')
      stub = routeguide_pb2_grpc.RouteGuideStub(channel)
      print("-------------- GetFeature --------------")
      response = stub.GetFeature(routeguide_pb2.Point(latitude=409146138, longitude=-746188906))
      get_feature(response)
      response = stub.GetFeature(routeguide_pb2.Point(latitude=0, longitude=-0))
      get_feature(response)
      print("-------------- ListFeatures --------------")
      response = stub.ListFeature(routeguide_pb2.Rectangle(
          lo = routeguide_pb2.Point(latitude=400000000, longitude=-750000000),
          hi=routeguide_pb2.Point(latitude=420000000, longitude=-730000000)
      ))
      for feature in response:
          print("Feature called {name} at {location}".format(name=feature.name, location=feature.location))
      print("-------------- RecordRoute --------------")
      feature_list = routeguide_db.read_routeguide_db()
      route_iterator = generate_route(feature_list)
      response = stub.RecordRoute(route_iterator)
      print("point count: {point_count} feature count: {feature_count} distance: {distance} elapsed time:{elapsed_time}".format(
          point_count  = response.point_count,
          feature_count = response.feature_count,
          distance = response.distance,
          elapsed_time = response.elapsed_time
      ))
      print("-------------- RouteChat --------------")
      response = stub.RouteChat(generate_route_note())
      for msg in response:
          print("recived message {message} location {location}".format(
              message=msg.message, location=msg.location))
    if __name__ == '__main__':
      run()
    

    运行 python routeguide_grpc_server.pypython routeguide_grpc_client.py 就可以看到效果

    关于 protobuf 的更多物料:

  • Protobuf3语言指南

  • blog - 服务器开发系列 1: 完整的 protobuf 业务实战
  • blog - devops| 日志服务实践: 实战阿里云日志服务 sdk, 再探 protobuf

关于 python 实战 grpc 的更多物料:

进行API开发选gRPC还是HTTP APIs?

开始之前先看一下 gRPC 与带有 json 的 HTTP APIs 对比表格
1377250-20190402221115154-1611563738.png

gRPC的优势

性能

gRPC消息使用一种有效的二进制消息格式protobuf进行序列化。Protobuf在服务器和客户机上的序列化非常快。Protobuf序列化后的消息体积很小,能够有效负载,在移动应用程序等有限带宽场景中显得很重要。
gRPC是为HTTP/2而设计的,它是HTTP的一个主要版本,与HTTP 1.x相比具有显著的性能优势::

  • 二进制框架和压缩。HTTP/2协议在发送和接收方面都很紧凑和高效。
  • 通过单个TCP连接复用多个HTTP/2调用。多路复用消除了线头阻塞

    代码生成

    所有gRPC框架都为代码生成提供了一流的支持。gRPC开发的核心文件是*.proto文件 ,它定义了gRPC服务和消息的约定。根据这个文件,gRPC框架将生成服务基类,消息和完整的客户端代码。
    通过在服务器和客户端之间共享*.proto文件,可以从端到端生成消息和客户端代码。客户端的代码生成消除了客户端和服务器上的重复消息,并为您创建了一个强类型的客户端。无需编写客户端代码,可在具有许多服务的应用程序中节省大量开发时间。

    严格的规范

    不存在具有JSON的HTTP API的正式规范。开发人员不需要讨论URL,HTTP动词和响应代码的最佳格式。(想想,是用Post还是Get好?使用Get还是用Put好?一想到有选择恐惧症的你是不是又开了纠结,然后浪费了大量的时间)
    gRPC规范是规定有关gRPC服务必须遵循的格式。gRPC消除了争论并节省了开发人员的时间,因为gPRC在各个平台和实现之间是一致的。

    HTTP/2为长期的实时通信流提供了基础。gRPC通过HTTP/2为流媒体提供一流的支持。
    gRPC服务支持所有流组合:

  • 一元(没有流媒体)

  • 服务器到客户端流
  • 客户端到服务器流
  • 双向流媒体

    截至时间/超时和取消

    gRPC允许客户端指定他们愿意等待RPC完成的时间。该期限被发送到服务端,服务端可以决定在超出了限期时采取什么行动。例如,服务器可能会在超时时取消正在进行的gRPC / HTTP /数据库请求。
    通过子gRPC调用截至时间和取消操作有助于实施资源使用限制。

    推荐使用gRPC的场景

    gRPC非常适合以下场景:

  • 微服务 - gRPC设计为低延迟和高吞吐量通信。gRPC非常适用于效率至关重要的轻型微服务。

  • 点对点实时通信 - gRPC对双向流媒体提供出色的支持。gRPC服务可以实时推送消息而无需轮询。
  • 多语言混合开发环境 - gRPC工具支持所有流行的开发语言,使gRPC成为多语言开发环境的理想选择。
  • 网络受限环境 - 使用Protobuf(一种轻量级消息格式)序列化gRPC消息。gRPC消息始终小于等效的JSON消息。

    gRPC的弱点

    浏览器支持有限

    当下,不可能直接从浏览器调用gRPC服务。gRPC大量使用HTTP/2功能,没有浏览器提供支持gRPC客户机的Web请求所需的控制级别。例如,浏览器不允许调用者要求使用的HTTP/2,或者提供对底层HTTP/2框架的访问。
    gRPC Web是gRPC团队的一项附加技术,它在浏览器中提供有限的gRPC支持。gRPC Web由两部分组成:支持所有现代浏览器的JavaScript客户端和服务器上的gRPC Web代理。gRPC Web客户端调用代理,代理将在gRPC请求上转发到gRPC服务器。
    gRPC Web并非支持所有gRPC功能。不支持客户端和双向流,并且对服务器流的支持有限。

    不是人类可读的

    HTTP API请求以文本形式发送,可以由人读取和创建。
    默认情况下,gRPC消息使用protobuf编码。虽然protobuf的发送和接收效率很高,但它的二进制格式是不可读的。protobuf需要在*.proto文件中指定的消息接口描述才能正确反序列化。需要额外的工具来分析线路上的Protobuf有效负载,并手工编写请求。
    存在诸如服务器反射和gRPC命令行工具等功能,以帮助处理二进制protobuf消息。另外,Protobuf消息支持与JSON之间的转换。内置的JSON转换提供了一种有效的方法,可以在调试时将Protobuf消息转换为可读的形式。

    不建议使用gRPC的场景

    在以下场景中,建议使用其他框架而不是gRPC:

  • 浏览器可访问的API - 浏览器不完全支持gRPC。gRPC-Web可以提供浏览器支持,但它有局限性并引入了服务器代理。

  • 广播实时通信 - gRPC支持通过流媒体进行实时通信,但不存在向已注册连接广播消息的概念。例如,在应该将新聊天消息发送到聊天室中的所有客户端的聊天室场景中,需要每个gRPC呼叫以单独地将新的聊天消息流传输到客户端。对于这种场景,SignalR是这种情况的有用框架。SignalR具有持久连接的概念和对广播消息的内置支持。
  • 进程间通信 - 进程必须承载HTTP/2服务才能接受传入的gRPC调用。对于Windows,进程间通信管道是一种快速,轻量级的通信方法。

文中大多内容来自于https://docs.microsoft.com/en-us/aspnet/core/gRPC/comparison?view=aspnetcore-3.0

用Python进行gRPC接口测试:
http://www.51testing.com/html/93/n-4463993.html?nomobile=1
http://www.51testing.com/html/11/n-4464411.html?nomobile=1