第九十五篇 gRPC快速入门

一、gRPC介绍

gRPC 是一个高性能、通用的开源 RPC 框架,基于 HTTP2 协议标准设计开发,默认采用 Protocol Buffers 数据序列化协议,支持多种开发语言。

在 gRPC 中,客户端应用程序可以直接调用不同机器上的服务器应用程序上的方法,就像它是本地对象一样,使您更容易创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 基于定义服务的思想,指定可以远程调用的方法及其参数和返回类型。在服务端,服务端实现这个接口并运行一个 gRPC 服务器来处理客户端调用。在客户端,客户端有一个存根(在某些语言中仅称为客户端),它提供与服务器相同的方法。

在这里插入图片描述

RPC 框架的目标就是让远程服务调用更加简单、透明,其负责屏蔽底层的传输方式(TCP/UDP)、序列化方式(XML/Json)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

二、 特点和优势

gRPC 的功能优点:

  • 高兼容性、高性能、使用简单

gRPC 的组成部分:

  • 使用 http2 作为网络传输层

  • 使用 protobuf 这个高性能的数据包序列化协议

  • 通过 protoc gprc 插件生成易用的 SDK

gRPC 的通信方式:

  • 客服端一次请求, 服务器一次应答

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

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

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

ProtoBuf buffer 是一种数据表达方式,以 .proto 结尾的数据文件,我们可以将其类比为 json、xml 等文件。针对 ProtoBuf buffer 数据源,可以利用 protoc 工具来生成各种语言的访问类。其优点在于,编解码速度更快且传输的数据更小。

在这里插入图片描述

在这里插入图片描述

HTTP/2 特性

新的二进制(Binary Format)分帧层。HTTP1.x的解析基于文本,文本的展现形式多样,要做到健壮性考虑的场景必然很多,二进制则只有0和1,更高效健壮在效率和正确性上都得到了保证。

多路复用;
头部压缩;
请求划分优先级;
服务器推送流(即Server Push技术)

三、gRPC初体验

  1. 定义.proto 文件

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;

// The greeting service definition.
service Greeter {
  // 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. 使用 protoc 编译
  • –python_out:编译生成处理 protobuf 相关的代码的路径

  • –grpc_python_out:编译生成处理 gRPC 相关的代码的路径

  • -I:指定 proto 的文件路径

# 编译 proto 文件
$ python -m grpc_tools.protoc \
    --python_out=. --grpc_python_out=. -I. \
    helloworld.proto

# 编译后自动生成的代码
helloworld_pb2.py
helloworld_pb2_grpc.py

编写server 服务代码
greeter_server.py

"""The Python implementation of the GRPC helloworld.Greeter server."""

from concurrent import futures
import logging

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, %s!' % 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()
    server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    serve()

客户端代码:
greeter_client.py

"""The Python implementation of the GRPC helloworld.Greeter client."""

from __future__ import print_function

import logging

import grpc
import helloworld_pb2
import helloworld_pb2_grpc


def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    # 连接 RPC 服务器
    with grpc.insecure_channel('localhost:50051') as channel:
        # 调用 RPC 服务
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
    print("Greeter client received: " + response.message)


if __name__ == '__main__':
    logging.basicConfig()
    run()

运行示例:

# 服务端
python greeter_server.py

#客户端
python greeter_client.py

四、 gRPC用于大数据量传输

demo.proto


// 语法版本声明,必须放在非注释的第一行
// Syntax version declaration. Must be placed on the first line of non-commentary.

syntax = "proto3";
// The document of proto3: https://developers.google.com/protocol-buffers/docs/proto3

// 包名定义, Python中使用时可以省略不写
// Package name definition, which can be omitted in Python.
package demo;

/*
`message`是用来定义传输的数据的格式的, 等号后面的是字段编号
消息定义中的每个字段都有唯一的编号
总体格式类似于Python中定义一个类或者Golang中定义一个结构体
*/
/*
`message` is used to define the structure of the data to be transmitted, after the equal sign
is the field number. Each field in the message definition has a unique number.
The overall format is similar to defining a class in Python or a structure in Golang.
*/
message Request {
    int64 client_id = 1;
    string request_data = 2;
}

message Response {
    int64 server_id = 1;
    string response_data = 2;
}

// `service` 是用来给gRPC服务定义方法的, 格式固定, 类似于Golang中定义一个接口
// `service` is used to define methods for gRPC services in a fixed format, similar to defining
//an interface in Golang
service GRPCDemo {
    // 一元模式(在一次调用中, 客户端只能向服务器传输一次请求数据, 服务器也只能返回一次响应)
    // unary-unary(In a single call, the client can only send request once, and the server can
    // only respond once.)
    rpc SimpleMethod (Request) returns (Response);

    // 客户端流模式(在一次调用中, 客户端可以多次向服务器传输数据, 但是服务器只能返回一次响应)
    // stream-unary (In a single call, the client can transfer data to the server several times,
    // but the server can only return a response once.)
    rpc ClientStreamingMethod (stream Request) returns (Response);

    // 服务端流模式(在一次调用中, 客户端只能一次向服务器传输数据, 但是服务器可以多次返回响应)
    // unary-stream (In a single call, the client can only transmit data to the server at one time,
    // but the server can return the response many times.)
    rpc ServerStreamingMethod (Request) returns (stream Response);

    // 双向流模式 (在一次调用中, 客户端和服务器都可以向对方多次收发数据)
    // stream-stream (In a single call, both client and server can send and receive data
    // to each other multiple times.)
    rpc BidirectionalStreamingMethod (stream Request) returns (stream Response);
}

server.py

"""The example of four ways of data transmission using gRPC in Python."""

from concurrent import futures
from threading import Thread

import grpc

import demo_pb2
import demo_pb2_grpc

__all__ = 'DemoServer'
SERVER_ADDRESS = 'localhost:23333'
SERVER_ID = 1


class DemoServer(demo_pb2_grpc.GRPCDemoServicer):

    # 一元模式(在一次调用中, 客户端只能向服务器传输一次请求数据, 服务器也只能返回一次响应)
    # unary-unary(In a single call, the client can only send request once, and the server can
    # only respond once.)
    def SimpleMethod(self, request, context):
        print("SimpleMethod called by client(%d) the message: %s" %
              (request.client_id, request.request_data))
        response = demo_pb2.Response(
            server_id=SERVER_ID,
            response_data="Python server SimpleMethod Ok!!!!")
        return response

    # 客户端流模式(在一次调用中, 客户端可以多次向服务器传输数据, 但是服务器只能返回一次响应)
    # stream-unary (In a single call, the client can transfer data to the server several times,
    # but the server can only return a response once.)
    def ClientStreamingMethod(self, request_iterator, context):
        print("ClientStreamingMethod called by client...")
        for request in request_iterator:
            print("recv from client(%d), message= %s" %
                  (request.client_id, request.request_data))
        response = demo_pb2.Response(
            server_id=SERVER_ID,
            response_data="Python server ClientStreamingMethod ok")
        return response

    # 服务端流模式(在一次调用中, 客户端只能一次向服务器传输数据, 但是服务器可以多次返回响应)
    # unary-stream (In a single call, the client can only transmit data to the server at one time,
    # but the server can return the response many times.)
    def ServerStreamingMethod(self, request, context):
        print("ServerStreamingMethod called by client(%d), message= %s" %
              (request.client_id, request.request_data))

        # 创建一个生成器
        # create a generator
        def response_messages():
            for i in range(5):
                response = demo_pb2.Response(
                    server_id=SERVER_ID,
                    response_data=("send by Python server, message=%d" % i))
                yield response

        return response_messages()

    # 双向流模式 (在一次调用中, 客户端和服务器都可以向对方多次收发数据)
    # stream-stream (In a single call, both client and server can send and receive data
    # to each other multiple times.)
    def BidirectionalStreamingMethod(self, request_iterator, context):
        print("BidirectionalStreamingMethod called by client...")

        # 开启一个子线程去接收数据
        # Open a sub thread to receive data
        def parse_request():
            for request in request_iterator:
                print("recv from client(%d), message= %s" %
                      (request.client_id, request.request_data))

        t = Thread(target=parse_request)
        t.start()

        for i in range(5):
            yield demo_pb2.Response(
                server_id=SERVER_ID,
                response_data=("send by Python server, message= %d" % i))

        t.join()


def main():
    server = grpc.server(futures.ThreadPoolExecutor())

    demo_pb2_grpc.add_GRPCDemoServicer_to_server(DemoServer(), server)

    server.add_insecure_port(SERVER_ADDRESS)
    print("------------------start Python GRPC server")
    server.start()
    server.wait_for_termination()

    # If raise Error:
    #   AttributeError: '_Server' object has no attribute 'wait_for_termination'
    # You can use the following code instead:
    # import time
    # while 1:
    #     time.sleep(10)


if __name__ == '__main__':
    main()

client.py

"""The example of four ways of data transmission using gRPC in Python."""

import time

import grpc

import demo_pb2
import demo_pb2_grpc

__all__ = [
    'simple_method', 'client_streaming_method', 'server_streaming_method',
    'bidirectional_streaming_method'
]

SERVER_ADDRESS = "localhost:23333"
CLIENT_ID = 1

# 中文注释和英文翻译
# Note that this example was contributed by an external user using Chinese comments.
# In all cases, the Chinese comment text is translated to English just below it.


# 一元模式(在一次调用中, 客户端只能向服务器传输一次请求数据, 服务器也只能返回一次响应)
# unary-unary(In a single call, the client can only send request once, and the server can
# only respond once.)
def simple_method(stub):
    print("--------------Call SimpleMethod Begin--------------")
    request = demo_pb2.Request(client_id=CLIENT_ID,
                               request_data="called by Python client")
    response = stub.SimpleMethod(request)
    print("resp from server(%d), the message=%s" %
          (response.server_id, response.response_data))
    print("--------------Call SimpleMethod Over---------------")


# 客户端流模式(在一次调用中, 客户端可以多次向服务器传输数据, 但是服务器只能返回一次响应)
# stream-unary (In a single call, the client can transfer data to the server several times,
# but the server can only return a response once.)
def client_streaming_method(stub):
    print("--------------Call ClientStreamingMethod Begin--------------")

    # 创建一个生成器
    # create a generator
    def request_messages():
        for i in range(5):
            request = demo_pb2.Request(
                client_id=CLIENT_ID,
                request_data=("called by Python client, message:%d" % i))
            yield request

    response = stub.ClientStreamingMethod(request_messages())
    print("resp from server(%d), the message=%s" %
          (response.server_id, response.response_data))
    print("--------------Call ClientStreamingMethod Over---------------")


# 服务端流模式(在一次调用中, 客户端只能一次向服务器传输数据, 但是服务器可以多次返回响应)
# unary-stream (In a single call, the client can only transmit data to the server at one time,
# but the server can return the response many times.)
def server_streaming_method(stub):
    print("--------------Call ServerStreamingMethod Begin--------------")
    request = demo_pb2.Request(client_id=CLIENT_ID,
                               request_data="called by Python client")
    response_iterator = stub.ServerStreamingMethod(request)
    for response in response_iterator:
        print("recv from server(%d), message=%s" %
              (response.server_id, response.response_data))

    print("--------------Call ServerStreamingMethod Over---------------")


# 双向流模式 (在一次调用中, 客户端和服务器都可以向对方多次收发数据)
# stream-stream (In a single call, both client and server can send and receive data
# to each other multiple times.)
def bidirectional_streaming_method(stub):
    print(
        "--------------Call BidirectionalStreamingMethod Begin---------------")

    # 创建一个生成器
    # create a generator
    def request_messages():
        for i in range(5):
            request = demo_pb2.Request(
                client_id=CLIENT_ID,
                request_data=("called by Python client, message: %d" % i))
            yield request
            time.sleep(1)

    response_iterator = stub.BidirectionalStreamingMethod(request_messages())
    for response in response_iterator:
        print("recv from server(%d), message=%s" %
              (response.server_id, response.response_data))

    print("--------------Call BidirectionalStreamingMethod Over---------------")


def main():
    with grpc.insecure_channel(SERVER_ADDRESS) as channel:
        stub = demo_pb2_grpc.GRPCDemoStub(channel)

        simple_method(stub)

        client_streaming_method(stub)

        server_streaming_method(stub)

        bidirectional_streaming_method(stub)


if __name__ == '__main__':
    main()

五、gRPC应用场景

RPC 的使用场景:分布式系统
随着微服务的不断发展,基于语言中立性的原则构建微服务,逐渐成为一种主流设计模式。例如对于后端并发处理要求高的微服务,比较适合采用 Go 语言构建,而对于前端的 Web 界面,则更适合 JavaScript。因此,基于多语言的 gRPC 框架来构建微服务,是一种比较好的技术选择。

在这里插入图片描述
gRPC Kubernetes
在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值