gRPC和protocol buffers。
gRPC可以使用 protocol buffers作为其接口定义语言(IDL)和基础消息交换格式
在gRPC中,客户端应用程序可以不同的机器上像调用本地方法一样,直接调用服务器应用程序上的方法,使得更容易创建分布式应用和服务。
gRPC是基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。在服务端,服务端实现此接口并运行gRPC服务器来处理客户端调用。在客户端,客户端有一个stub,提供与服务器相同的方法。
1)使用protocol buffers
默认情况下,gRPC使用protocol buffers,其是谷歌用于序列化结构化数据的开源框架
第一步:在一个proto 文件中定义你想要序列化的数据的结构
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
第二步:使用protocol buffers编译器从proto文件中以首选语言生成数据访问类,这为每个字段提供了简单的访问器,以及将整个结构序列化/解析为原始字节的方法。
例如,如果选择的语言是C++,那么在上面的示例中运行编译器后,将生成一个可被调用的类。可以在应用程序中使用这个类来填充、序列化和检索protocol buffer message。
在普通的proto文件中定义gRPC服务,使用RPC方法参数和返回类型来作为protocol buffers的message:
#The greater service definition
service Greater {
//sends a greeting
rpc SayHello (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;
}
介绍gRPC的核心概念、架构和生命周期
1)服务定义
与许多RPC系统一样,gRPC基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。缺省情况下,gRPC使用protocol buffers作为接口定义语言(IDL)来描述服务接口和负载消息的结构。如果需要,可以使用其他替代方案。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
gRPC允许定义四种服务方法:
rpc SayHello(HelloRequest) returns (HelloResponse);
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
2)使用API
从文件中的服务定义开始,gRPC提供协议缓冲区编译器插件来生成客户端和服务器端代码。gRPC用户通常在客户端调用这些API,并在服务器端实现相应的API
3)RPC生命周期
深入了解当gRPC客户机调用gRPC服务器方法时会发生什么
1. Unary RPCs
a)一旦客户端调用了Stub方法,服务端就会收到RPC调用的通知,其中包括该调用的客户端元数据、方法名称和指定的截止日期
b)服务端可以直接发送回自己的初始元数据(在任何响应之前发送),或者等待客户端的请求消息。
c)一旦服务端获得了客户端的请求消息,他就会执行创建和填充响应所需的任何工作。然后将响应连同状态详细信息以及可选的尾部元数据一起返回给客户端
d)如果响应状态为OK,则客户端获得响应,完成客户端的调用
2. Server streaming RPCs
Server streaming RPC类似于Unary RPC,不同之处在于服务器在响应客户端的请求时返回消息流。发送完所有消息后,服务器的状态详细信息(状态码和可选状态消息)和可选的尾随元数据被发送到客户端。这就完成了服务器端的处理。客户端在拥有所有服务器消息后完成调用
3. Client streaming RPCs
Client streaming RPC类似于Unary RPC,不同之处是客户端向服务器发送消息流而不是单个消息。服务器用一条消息(连同它的状态详细信息和可选的尾随元数据)进行响应,通常是在它接收到所有客户机的消息之后,但不一定是这样。
4. Bidirectional streaming RPCs
在Bidirectional streaming RPC中,调用由调用方法的客户端和接收客户端元数据、方法名称和截止日期的服务器发起。服务器可以选择发回其初始元数据,或者等待客户端开始流式传输消息。
客户端和服务器端流处理是特定于应用程序的。由于这两个流是独立的,客户端和服务器可以以任何顺序读写消息。例如,服务器可以等到接收到客户端的所有消息后再写入消息,或者服务器和客户端可以玩“ping-pong”游戏——服务器收到请求,然后发回响应,然后客户端根据响应发送另一个请求,以此类推。
4)Deadline/Timeouts
gRPC允许客户端指定在RPC以错误终止之前他们愿意等待RPC完成的时间。在服务器端,服务器可以查询特定RPC是否超时,或者还剩多少时间来完成RPC。
指定Deadline或Timeouts是特定于语言的:一些语言api根据Timeouts(持续时间)工作,而一些语言api根据Deadline(固定的时间点)工作,可能有也可能没有默认的Deadline日期。
5)RPC 终止
在gRPC中,客户机和服务器都对调用的成功做出独立的本地决定,它们的结论可能不匹配。这意味着,例如,您可能有一个RPC在服务器端成功完成(“我已经发送了所有响应!”),但在客户端失败(“响应在我的截止日期之后到达!”)。服务器也有可能在客户端发送所有请求之前决定完成。
6)取消RPC
客户端或服务器都可以在任何时候取消RPC。取消将立即终止RPC,因此不再执行进一步的工作。
每种gRPC语言/平台都包括:
1.快速开始
2.手册
3.API参考
python的gRPC工具包括protocol buffer compiler和根据.proto文件中服务定义生成服务端和客户端代码的特殊插件
①安装gRPC和gRPC tools
pip install grpcio
pip install grpcio_tools
在site-package目录下会生成google、grpc、grpc_tools等文件夹
②测试
②.1 编写.proto文件——helloworld.proto——定义服务接口以及消息格式
编写.proto文件用的是 protobuffer 接口描述语言(IDL)
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
②.2 编译proto文件——生成客户端和服务端要调用的API
python -m grpc_tools.protoc -I../protos --python_out=. --pyi_out=. --grpc_python_out=. ../protos/helloworld.proto
生成xxxx_pb2.py以及xxxx_pb2_grpc.py文件,前者包含request和response classes,后者包含client和server classes,这里xxxx是proto文件名决定的,pb2_grpc.py中调用pb2.py的API。
helloworld_pb2.py
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: helloworld.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2\x8e\x01\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x12\x43\n\rSayHelloAgain\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x42\x36\n\x1bio.grpc.examples.helloworldB\x0fHelloWorldProtoP\x01\xa2\x02\x03HLWb\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'helloworld_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\033io.grpc.examples.helloworldB\017HelloWorldProtoP\001\242\002\003HLW'
_HELLOREQUEST._serialized_start=32
_HELLOREQUEST._serialized_end=60
_HELLOREPLY._serialized_start=62
_HELLOREPLY._serialized_end=91
_GREETER._serialized_start=94
_GREETER._serialized_end=236
# @@protoc_insertion_point(module_scope)
helloworld_pb2_grpc.py文件。
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import helloworld_pb2 as helloworld__pb2
# which can be used by clients to invoke Grreet RPCs
class GreeterStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.SayHello = channel.unary_unary(
'/helloworld.Greeter/SayHello',
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
response_deserializer=helloworld__pb2.HelloReply.FromString,
)
self.SayHelloAgain = channel.unary_unary(
'/helloworld.Greeter/SayHelloAgain',
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
response_deserializer=helloworld__pb2.HelloReply.FromString,
)
# 定义了HelloWorld服务实现的接口
class GreeterServicer(object):
"""Missing associated documentation comment in .proto file."""
def SayHello(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def SayHelloAgain(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
# 添加GreeterServicer到一个grpc server
def add_GreeterServicer_to_server(servicer, server):
rpc_method_handlers = {
'SayHello': grpc.unary_unary_rpc_method_handler(
servicer.SayHello,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
'SayHelloAgain': grpc.unary_unary_rpc_method_handler(
servicer.SayHelloAgain,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'helloworld.Greeter', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Greeter(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def SayHello(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHello',
helloworld__pb2.HelloRequest.SerializeToString,
helloworld__pb2.HelloReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@staticmethod
def SayHelloAgain(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHelloAgain',
helloworld__pb2.HelloRequest.SerializeToString,
helloworld__pb2.HelloReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
②.3 创建服务端——greeter_server.py——用到了pb2_grpc中的Servicer接口以及add_XxxServicer_to_server接口
from concurrent import futures
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
return helloworld_pb2.HelloReply(message=f'Hello, {request.name}!')
def SayHelloAgain(self, request, context):
return helloworld_pb2.HelloReply(message=f'Hello again, {request.name}!')
def serve():
port = "50051"
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:' + port)
server.start()
print("Server started, listening on " + port)
server.wait_for_termination()
if __name__ == '__main__':
logging.basicConfig()
serve()
②.4 创建客户端——greeter_client.py——用到了pb2_grpc中的Stub接口
from __future__ import print_function
import logging
import grpc
import helloworld_pb2
import helloworld_pb2_grpc
def run():
with grpc.insecure_channel('172.18.52.52:50051') as channel:
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)
if __name__ == '__main__':
logging.basicConfig()
run()
Basics tutorial | Python | gRPC
1)概述
ALTS是一个由google开发的相互认证和传输加密系统,被用于保护google基础设施中的RPC通信安全。ALTS类似于相互TLS,但是经过了设计和优化,以满足google生产环境的需要。
gRPC中的ALTS具有以下特点:
①创建使用ALTS作为传输安全协议的gRPC服务端和客户端
②ALTS连接是端到端的,具有隐私和完整性保护
③支持客户端认证和服务端认证
④更改最少的代码来启动ALTS
gPRC用户可以通过几行代码来配置ALTS作为传输安全协议。
2)使用ALTS传输安全协议的gRPC客户端
使用ALTS之前,客户端是通过insecure_channel和服务端建立连接通道的,这时候我们可以使用ALTS凭证连接到服务端
import grpc
channel_creds = grpc.alts_channel_credentials()
channel = grpc.secure_channel(address, channel_creds)
3)使用ALTS传输安全协议的gRPC服务端
gRPC服务器可以使用ALTS凭证来允许客户端连接到它们:
import grpc
server = grpc.server(futures.ThreadPoolExecutor())
server_creds = grpc.alts_server_credentials()
server.add_secure_port(server_address, server_creds)
gRPC Python依赖于protocol buffers compiler(protoc)来生成代码。它使用一个插件,用grpc特定的代码 来补充 由普通protoc生成的代码。对于一个包含gRPC服务的.proto服务描述文件,该普通protoc生成的代码会在_pb2.py中生成,而特定于gRPC的代码会在_pb2_grpc.py文件中生成。后面一个py文件将会使用到前一个文件中的API。
这一节重点讲解_pb2.grpc.py文件——特定于gRPC的生成代码
1)以上面的helloworld proto 服务来说明
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
当该proto service被编译,gRPC协议插件生成的代码类似于_pb_grpc.py文件
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import helloworld_pb2 as helloworld__pb2
# which can be used by clients to invoke Grreet RPCs
class GreeterStub(object):
"""Missing associated documentation comment in .proto file."""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.SayHello = channel.unary_unary(
'/helloworld.Greeter/SayHello',
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
response_deserializer=helloworld__pb2.HelloReply.FromString,
)
self.SayHelloAgain = channel.unary_unary(
'/helloworld.Greeter/SayHelloAgain',
request_serializer=helloworld__pb2.HelloRequest.SerializeToString,
response_deserializer=helloworld__pb2.HelloReply.FromString,
)
# 定义了HelloWorld服务实现的接口
class GreeterServicer(object):
"""Missing associated documentation comment in .proto file."""
def SayHello(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def SayHelloAgain(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
# 添加GreeterServicer到一个grpc server
def add_GreeterServicer_to_server(servicer, server):
rpc_method_handlers = {
'SayHello': grpc.unary_unary_rpc_method_handler(
servicer.SayHello,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
'SayHelloAgain': grpc.unary_unary_rpc_method_handler(
servicer.SayHelloAgain,
request_deserializer=helloworld__pb2.HelloRequest.FromString,
response_serializer=helloworld__pb2.HelloReply.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'helloworld.Greeter', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Greeter(object):
"""Missing associated documentation comment in .proto file."""
@staticmethod
def SayHello(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHello',
helloworld__pb2.HelloRequest.SerializeToString,
helloworld__pb2.HelloReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@staticmethod
def SayHelloAgain(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/helloworld.Greeter/SayHelloAgain',
helloworld__pb2.HelloRequest.SerializeToString,
helloworld__pb2.HelloReply.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
2)代码元素
The gRPC generated code starts by importing the
grpc
package and the plain_pb2
module, synthesized byprotoc
, which defines non-gRPC-specific code elements, like the classes corresponding to protocol buffers messages and descriptors used by reflection.
gRPC生成的代码中先导入了grpc包和_pb2包,后者由protoc编译器生成,定义了非gRPC特定的代码元素,比如与协议缓冲区message和reflect使用的描述符相对应的类。
对于.proto文件中定义的每个Service都会生成三个主要元素:
Stub:GreeterStub被客户端用来和gRPC服务建立连接
Servicer:GreeterServicer被服务端用来实现gRPC服务
Registration Function:add_GreeterServicer_to_server函数被用来注册servicer(使用grpc.server对象)
Stub
生成的Stub类供gRPC客户端使用。它有一个接受grpc.Channel参数的构造函数。对于服务中的每个方法,构造函数会向stub对象添加具有相同名称的属性。
Servicer
对于每个服务,都会生成一个Servicer类,作为服务实现的父类。对于服务中的每个方法,都会在Servicer类中生成相应的函数。在服务端会实现方法来对该函数重写。与.proto文件中代码元素关联的注释在生成的python代码中以文档字符串的形式出现。
Registration Function
对于每个服务,都会生成一个注册函数,该函数负责注册一个在grpc.Server对象上实现它的Servicer对象,以便服务端可以将查询路由到各自的服务,这个函数接受一个实现Servicer的对象,通常是上述生成的Servicer代码元素的子类实例,还接受一个grpc.Server对象作为参数。
Welcome to gRPC Python’s documentation! — gRPC Python 1.54.0 documentation
gRPC Packages
gRPC支持跨不同的软硬件平台
每种gRPC语言/平台都包括:
1.快速开始
2.手册
3.API参考