gRPC学习

一、什么是gRPC?

1.1 gRPC简介

gRPCprotocol buffers

gRPC可以使用 protocol buffers作为其接口定义语言(IDL)和基础消息交换格式

1.1.1 概述

在gRPC中,客户端应用程序可以不同的机器上像调用本地方法一样,直接调用服务器应用程序上的方法,使得更容易创建分布式应用和服务。

gRPC是基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。在服务端,服务端实现此接口并运行gRPC服务器来处理客户端调用。在客户端,客户端有一个stub,提供与服务器相同的方法。

gRPC学习_第1张图片

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;
}

1.2 核心概念

介绍gRPC的核心概念、架构和生命周期

1.2.1 概述

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允许定义四种服务方法:

  • Unary RPCs:客户端向服务端发送单个请求并获得单个响应,就像普通的函数调用
rpc SayHello(HelloRequest) returns (HelloResponse);
  • Server Streaming RPCs:客户端向服务端发送请求并获得读取消息序列的流。客户端从返回的流中读取消息,直到没有更多的消息。gRPC保证了单个RPC调用中消息顺序。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  •  Client Streming RPCs:客户端写入消息序列并将它们发送到服务器,同样使用提供的流。一旦客户端完成了消息的写入,它将等待服务器读取消息并返回其响应。gRPC再次保证了单个RPC调用中的消息顺序。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • Bidirectional Streaming RPCs:双方使用读写流发送消息序列。这两个流独立运行,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以等待接收到所有客户端消息后再写入响应,或者它可以交替地读取消息然后写入消息,或者其他一些读写组合。保留了每个流中的消息顺序。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

2)使用API

从文件中的服务定义开始,gRPC提供协议缓冲区编译器插件来生成客户端和服务器端代码。gRPC用户通常在客户端调用这些API,并在服务器端实现相应的API

  • 在服务器端,服务器实现服务声明的方法,并运行gRPC服务器来处理客户端调用。gRPC基础结构解码传入请求,执行服务方法,并编码服务响应。
  • 在客户端,客户端有一个本地对象,称为Stub(对于某些语言,首选术语是客户端),它实现与服务相同的方法。然后,客户端可以在本地对象上调用这些方法,这些方法将调用的参数封装在适当的协议缓冲区消息类型中,将请求发送到服务器,并返回服务器的协议缓冲区响应。

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参考

2.1 C++

2.2 Java

2.3 Python

python的gRPC工具包括protocol buffer compiler和根据.proto文件中服务定义生成服务端和客户端代码的特殊插件

2.3.1 快速入门

①安装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文件,前者包含requestresponse classes,后者包含clientserver classes,这里xxxxproto文件名决定的,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()

2.3.2 手册

Basics tutorial | Python | gRPC

2.3.3 Application Layer Transport Security(ALTS)

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)

2.3.4 生成的代码参考解释

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 by protoc, 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对象作为参数。

2.3.5 API

Welcome to gRPC Python’s documentation! — gRPC Python 1.54.0 documentation

2.3.6 Daily Build

gRPC Packages

三、支持的平台 (感兴趣的可以看Supported platforms | gRPC)

gRPC支持跨不同的软硬件平台

每种gRPC语言/平台都包括:

1.快速开始

2.手册

3.API参考

  • Android
  • Web 
  • ……

你可能感兴趣的:(网络通信,分布式,网络协议,rpc)