grpc简介
乙醇 创建于 9 months 之前
最后更新: 9 months 之前
阅读数: 571
RPC
试想这样一种场景,一个复杂系统中的两个模块之前需要互相调用,一般的做法是什么?
可能这两个模块是跑在同一个进程上,那么通信起来其实是非常方便的,也有可能这两个模块分别是跑在不同的进程之上,那么就涉及到复杂一点的跨进程通信的技术了。但这些都是模块部署在同一机器下的情景,大家想象起来也会比较容易。
更加深入一些,如果两个模块跑在不同的机器之间,那么模块之前的调用如何实现呢?这就需要使用RPC技术了。
RPC(Remote Procedure Call)— 远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户端/服务器模式。请求程序就是一个客户端,而服务提供程序就是一个服务器。首先,客户端调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
简单来说RPC需要server端和client端,server端定义一些函数,client端通过网络请求去调用这些函数拿到返回值。server端和client端跑在不同的机器上,结合微服务的概念就是server端就是一个独立的微服务,其他微服务需要通过启动client端来调用该微服务提供的服务。
gRPC
gRPC 一开始由 Google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
组成
典型的grpc实现有两端组成,分别是
- server
- client
gRPC的特性
- 由于client和server需要通过网络进行消息的传递,那么网络协议成了grpc里重要的一环。grpc协议是HTTP/2,这是一种优化过的http协议,实现了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。
- 服务端向外提供了一些可供调用的函数,这些函数的原型通过ProtoBuf协议来进行定义。ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。
- 支持多种编程语言。比如支持golang/java/c++/ruby/python/nodejs等。
gRPC的优点
- 使用protobuf进行消息的序列化,压缩率高,性能好,毕竟压缩的越小在网络上传播的速度就相对会更快一点
- 序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射,其实除了可读性差之外,pb的使用方式跟json基本都差不多了
- 支持向前兼容和向后兼容,升级比较简单
- 支持多语言
典型的gRPC实现
典型的gRPC实现有3个部分,分别是
- 服务定义,使用protobuf的语法
- server端实现,可以使用任意支持的语言
- client端实现,可以使用任意支持的语言
这里我们简单演示一下如何使用python实现简单的grpc server和client
服务定义
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
server端实现
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)
def SayHelloAgain(self, request, context):
return helloworld_pb2.HelloReply(message='Hello again, %s!' % request.name)
...
client端实现
def run():
channel = grpc.insecure_channel('localhost:50051')
stub = helloworld_pb2_grpc.GreeterStub(channel)
response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
response = stub.SayHelloAgain(helloworld_pb2.HelloRequest(name='you'))
print("Greeter client received: " + response.message)
测试gRPC的server
其实跟接口测试的概念差不多,使用client stub去调用server,然后进行断言就好了。简而言之就是写代码去调用server端提供的函数,然后做断言
测试client
这一部分对大家来说可能不太好想象,有一种做法是mock client,实现client的一系列调用server的mock方法,然后把client代入正常的业务逻辑,最后做逻辑或流程正确与否的判断。
举个例子,比如有个微服务使用client调用了a和b两个函数,那么就mock掉a和b的client端实现,最后在正常的业务逻辑结束之后,断言client先调用了a再调用了b。这种mock的方式之前在做单元测试的时候非常的普遍,一般是用来mock掉网络请求或者是数据库连接,用在rpc的client测试上就显得比较有意思了。
性能测试
推荐使用ghz。
ghz · gRPC benchmarking and load testing tool
监控
一般可以使用下面的两种方案
- OpenCensus
- Prometheus
Tracing
因为微服务之前的调用链路很复杂,所以需要使用tracing来进行调用链的跟踪。这里可以简单的使用OpenCensus Jaeger exporter来实现。
grpc gateway
在测试和调式的时候,每次写client去调用server其实是一件比较麻烦的事情,grpc gateway提供了一种简单的方式把grpc转成restful形式的接口,这样就可以直接使用postman等工具进行调试和测试了。