小言_互联网的博客

TensorFlow Serving系列之gRPC基本知识

550人阅读  评论(0)

0 背景

在上一篇文章中,我们介绍了TFS利用gRPC接口进行服务访问,那么什么是gRPC呢,为什么要用gRPC接口,本文对gRPC的基础知识作一简单介绍,以及通过官网提供的例子进行简单的测试。

系列文章目录

(一)TensorFlow Serving系列之安装及调用方法

(二)TensorFlow Serving系列之导出自己的训练模型

(三)TensorFlow Serving系列之客户端gRPC调用

(四)TensorFlow Serving系列之gRPC基本知识

1 gRPC是什么

gRPC是由谷歌开发的一款语言中立、平台中立、开源的远程过程调用(RPC)系统。在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法,使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。如下图所示就是一个典型的gRPC结构图。

既然是server/client模型,那么我们直接用restful api不是也可以满足吗,为什么还需要RPC呢?gRPC和restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议,不过gRPC还是有些特有的优势,如下:

  • 基于HTTP/2 
    HTTP/2 提供了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。gRPC 的协议设计上使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。
  • IDL使用ProtoBuf 
    gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。
  • 多语言支持(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java) 
    gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。

在以下场景中,使用gRPC具有优势:

  • - 低延迟、高扩展性、分布式的系统 
  • - 同云服务器进行通信的移动应用客户端 
  • - 设计语言独立、高效、精确的新协议 
  • - 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等

2 环境配置

要首先安装grpc相关软件

pip install -U grpcio
pip install -U grpcio-tools
pip install -U protobuf

建议使用-U参数安装最新版本的软件包,如果protoc的版本有问题,会报如下错误

TypeError: __init__() got an unexpected keyword argument 'serialized_options'

3 使用流程

gRPC的使用通常包括如下几个步骤:

  • 通过protobuf来定义接口和数据类型
  • 编写gRPC server端代码
  • 编写gRPC client端代码

下面来通过一个helloworld实例来详细讲解上述的三步,目录结构如下

├── protos
│   └── helloworld.proto
└── rpc_package
    ├── hello_client.py
    ├── hello_server.py
    ├── helloworld_pb2_grpc.py
    ├── helloworld_pb2.py
    ├── __init__.py

3.1 定义接口和数据类型

生成helloworld.proto文件,内容如下

syntax = "proto3";

package rpc_package;

// define a service
service HelloWorldService {
    // define the interface and data type
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// define the data type of request
message HelloRequest {
    string name = 1;
}

// define the data type of response
message HelloReply {
    string message = 1;
}

然后使用gRPC protobuf生成工具生成对应的库函数

python -m grpc_tools.protoc -I=./protos --python_out=./rpc_package --grpc_python_out=./rpc_package ./protos/helloword.proto

其中:

-I指定了源文件的路径,

--python_out,指定 xxxx_pb2.py的输出路径,如果使用其它语言请使用对应语言的option

--grpc_python_out指定xxxx_pb2_grpc.py文件的输出路径

--*.proto是要编译的proto文件。

运行成功后,会自动在rpc_package文件夹中生成helloworld_pb2.py(里面有消息序列化类)和helloworld_pb2_grpc.py(包含了服务器 Stub 类和客户端 Stub 类,以及待实现的服务 RPC 接口)。

3.2 服务端代码

# -*-coding: utf-8 -*-

from concurrent import futures
import grpc
import logging
import time

from helloworld_pb2_grpc import add_HelloWorldServiceServicer_to_server, \
    HelloWorldServiceServicer
from helloworld_pb2 import HelloRequest, HelloReply


class Hello(HelloWorldServiceServicer):

    # 这里实现我们定义的接口
    def SayHello(self, request, context):
        return HelloReply(message='Hello, %s!' % request.name)


def serve():
    # 这里通过thread pool来并发处理server的任务
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

    # 将对应的任务处理函数添加到rpc server中
    add_HelloWorldServiceServicer_to_server(Hello(), server)

    # 这里使用的非安全接口,实际上gRPC支持TLS/SSL安全连接,以及各种鉴权机制
    server.add_insecure_port('[::]:50000')
    server.start()
    try:
        while True:
            time.sleep(60 * 60 * 24)
    except KeyboardInterrupt:
        server.stop(0)


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

3.3 客户端代码

# -*- coding: utf-8 -*-

from __future__ import print_function
import logging

import grpc
from helloworld_pb2 import HelloRequest, HelloReply
from helloworld_pb2_grpc import HelloWorldServiceStub

def run():
    # 使用with语法保证channel自动close
    with grpc.insecure_channel('localhost:50000') as channel:
        # 客户端通过stub来实现rpc通信
        stub = HelloWorldServiceStub(channel)

        # 客户端必须使用定义好的类型,这里是HelloRequest类型
        response = stub.SayHello(HelloRequest(name='zongxp'))
    print ("hello client received: " + response.message)

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

4 测试

首先运行服务端代码

python hello_server.py

然后运行客户端代码

python hello_client.py

输出如下

hello client received: Hello, zongxp!


转载:https://blog.csdn.net/zong596568821xp/article/details/101691853
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场