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/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的使用等。
安装:
gRPC 的安装:
pip install grpcio
安装 ProtoBuf 相关的 python 依赖库:
pip install protobuf
安装 python grpc 的 protobuf 编译工具:
pip install grpcio-tools
实践:
下面我们使用 gRPC 定义一个接口,该接口实现对传入的数据进行大写的格式化处理。
- 创建项目 python demo 工程:
- client目录下的 main.py 实现了客户端用于发送数据并打印接收到 server 端处理后的数据。
- server 目录下的 main.py 实现了 server 端用于接收客户端发送的数据,并对数据进行大写处理后返回给客户端。
- example 包用于编写 proto 文件并生成 data 接口。
- 定义 gRPC 接口:
syntax = "proto3";
package example;
service FormatData {
rpc DoFormat(Data) returns (Data){}
}
message Data {
string text = 1;
}
编译 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 的基础: 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 文件在当前目录
helloworld_pb2.py
: 用来和 protobuf 数据进行交互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.py
和 python 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.py
和python routeguide_grpc_client.py
就可以看到效果关于 protobuf 的更多物料:
- blog - 服务器开发系列 1: 完整的 protobuf 业务实战
- blog - devops| 日志服务实践: 实战阿里云日志服务 sdk, 再探 protobuf
关于 python 实战 grpc 的更多物料:
进行API开发选gRPC还是HTTP APIs?
开始之前先看一下 gRPC 与带有 json 的 HTTP APIs 对比表格
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