首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【译】基于python 的 RPC 框架比较: gRPC vs Thrift vs RPyC

【译】基于python 的 RPC 框架比较: gRPC vs Thrift vs RPyC

作者头像
goodspeed
发布2021-08-18 17:32:59
6.5K0
发布2021-08-18 17:32:59
举报
文章被收录于专栏:厉害了程序员厉害了程序员

原文地址:https://www.hardikp.com/2018/07/28/services/

引言

那一年是2015年。我正在写一堆ML训练脚本以及几个生产脚本。他们都需要金融数据。数据分散在多个表和多个数据存储中。日内市场数据以不同方式存储在cassandra集群中,而每日/每月的数据则在MySQL数据库中。同样地,不同类型的证券(期货、期权、股票等)被存储在不同的位置。

所以,我决定写一个可以在我的脚本中使用的数据操作库。结果这个数据操作库在我的团队中相当受欢迎。它拥有我们当时需要的所有东西:

  • 所有数据类型的单一接口 - 来自不同交易所的期货、股票、ETF、货币、指数和基金。
  • 易于使用的接口
  • 在支持的数据间隔方面很灵活。它在日内、跨日和跨月的时间段里工作得完美无缺
  • 它既可用于实时数据获取/使用,也可用于历史数据需求
  • 很容易支持一种新的数据类型——例如,宏观经济指标

然而,它有一些我当时无法预见的致命的缺陷。随着时间的推移,依赖这个库的生产脚本的数量成倍增长。而我们的数据操作库直接调用数据库查询。

  • 更改数据库中的任何内容都会破坏现有的生产流程。因此,没有办法在不造成停机的情况下更改数据库
  • 此外,迅速增加的生产进程对数据库造成了压力。由于数据库的访问被细化到代码库的其他部分,所以不可能进行适当的优化或负载平衡。

大约一年前,有人问我,我们是否应该把那个代码库转换为服务。我对此置之不理--完全没有意识到在接下来的一年里我将面临的问题。说实话,那时候我还没有完全理解服务或微服务--这让我对它用于数据获取这样的事情持怀疑态度。我仍然相信,将这些代码作为一个库是灵活性和快速变化的保证。

但是,几天前我终于开始重新审视这些服务。在过去的几天里,我看了gRPC、Thrift和RPyC。我在这篇文章中总结了我的初步结论。因为我主要是用python来做所有事情,所以我是从这个角度来看待这些框架的。

您可以在这个链接中找到后续示例的代码。

gRPC

gGPC使用Protocal Buffers 进行序列化和反序列化。它是由谷歌开发的--他们在重写内部框架stubby的时候将其作为一个开源软件发布。目前,包括Netflix和Square在内的一些公司正在使用这个框架来实现他们的服务。

让我们直接跳到最简单的例子中。

我们将为所有3个框架使用相同的玩具示例:

  • 我们将定义一个名为Time 的服务。
  • 它实现了一个单一的 RPC 调用:GetTime.
  • GetTime 不接受任何参数并以字符串格式返回当前的服务器时间。

简单的 gRPC 示例

创建一个 time.proto Protocol Buffers文件来描述我们的服务。

syntax = "proto3"; 
package time; 


service Time { // Time 服务名
    // GetTime RPC 调用
    // TimeRequest RPC 输入类型
    // TimeReply   RPC 输出类型
    rpc GetTime (TimeRequest) returns (TimeReply) {} 
} 

// Empty Request Message 
message TimeRequest {
} 

// The response message containing the time 
message TimeReply {
    string message = 1; // 字符串类型
}

下面是对上面代码的一点解释。

现在,使用上面的 protobuf 文件生成 python 文件 time_pb2.py 和 time_pb2_grpc.py。我们将在服务器和客户端代码中使用它们。下面是执行此操作的命令行代码(您将需要 grpcio-tools python 包) :

p

python -m grpc_tools.protoc --python_out=. --grpc_python_out=. time.proto

创建服务器脚本 server.py。

import time
from concurrent import futures

import grpc

# import 生成的代码
import time_pb2
import time_pb2_grpc

_ONE_DAY_IN_SECONDS = 60 * 60 * 24


# 定义 Timer 类
class Timer(time_pb2_grpc.TimeServicer):
    def GetTime(self, request, context): # 定义RPC 调用
        return time_pb2.TimeReply(message=time.ctime()) # 返回当前时间


def serve():
    # 创建一个线程池,添加我们的服务实例并启动服务器
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    time_pb2_grpc.add_TimeServicer_to_server(Timer(), server)
    server.add_insecure_port('[::]:50051') 
    server.start()
    try:
        while True:# sleep 避免主线程退出
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        server.stop(0)


if __name__ == '__main__':
    serve()

下面是带注释的服务器代码:

Add client code to theclient.pyfile.

将客户端代码添加到 client.py 文件。

import grpc

import time_pb2
import time_pb2_grpc


def run():
    channel = grpc.insecure_channel('localhost:50051') # 连接服务器
    stub = time_pb2_grpc.TimeStub(channel)
    response = stub.GetTime(time_pb2.TimeRequest()) # 调用RPC
    print('Client received: {}'.format(response.message))


if __name__ == '__main__':
    run()

我在下面添加了注释客户机代码。

更多细节

gRPC 使用 HTTP/2进行客户机-服务器通信,每个 RPC 调用都是同一个 TCP/IP 连接中的单独的流。

支援4种不同类型的RPCs:

  • Unary RPC - a single request followed by a sing单一的 RPC ——一个请求后跟一个来自服务器的响应。我们的 TimeService 示例使用单一的 RPC。
rpc GetTime (TimeRequest) returns (TimeReply) {}
  • 服务器流 RPC-客户端发送一个请求并获得一个可读取的流。
 rpc GetTime (TimeRequest) returns (stream TimeReply) {}
  • 客户端流式 RPC-客户端写入一个消息序列。
rpc GetTime (stream TimeRequest) returns (TimeReply) {}
  • 双向流式 RPC——双方使用读写流发送一系列消息。
rpc GetTime (stream TimeRequest) returns (stream TimeReply) {}

带有内置的超时功能,这在实践中相当方便。许多应用程序要求在一定的时间间隔内做出响应。

优缺点

优点:

  • 为服务器和客户端提供多语言支持
  • 默认情况下,连接使用 HTTP/2
  • 丰富的文档
  • 这个项目得到了谷歌和其他公司的积极支持

缺点:

  • 灵活性较低(特别是与rpyc).

链接:

  • 官方网站及教程 -https://grpc.io/docs/guides/.
  • gRPC Concepts.

Thrift

Thrift在Facebook和Hadoop/Java服务世界中相当流行。它是在Facebook创建的,他们在某个时候把它作为一个Apache项目开源了。

简单的thrift例子

使用Thrift接口描述语言(IDL)创建描述接口的time_service.thrift文件。

service TimeService {
    string get_time()
}

运行以下命令生成 python 代码。它将创建一个 gen-py 目录。我们将使用它来构建服务器和客户端脚本。

thrift -r --gen py time_service.thrift

用 server.py 编写以下服务器代码。

import sys
import time

from thrift.protocol import TBinaryProtocol
from thrift.server import TServer
from thrift.transport import TSocket, TTransport
sys.path.append('gen-py')
from time_service import TimeService


class TimeHandler:
    def __init__(self):
        self.log = {}

    def get_time(self):
        return time.ctime()


if __name__ == '__main__':
    handler = TimeHandler()
    processor = TimeService.Processor(handler)
    transport = TSocket.TServerSocket(host='127.0.0.1', port=9090)
    tfactory = TTransport.TBufferedTransportFactory()
    pfactory = TBinaryProtocol.TBinaryProtocolFactory()

    server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)

    print('Starting the server...')
    server.serve()
    print('done.')

在 client.py 中编写以下代码。

import sys

from thrift import Thrift
from thrift.protocol import TBinaryProtocol
from thrift.transport import TSocket, TTransport
sys.path.append('gen-py')
from time_service import TimeService


def main():
    # 创建 socket
    transport = TSocket.TSocket('localhost', 9090)

    # Buffering 是关键. 原始套接字非常慢
    transport = TTransport.TBufferedTransport(transport)

    # 以协议方式包装
    protocol = TBinaryProtocol.TBinaryProtocol(transport)

    # 创建一个client 
    client = TimeService.Client(protocol)

    # Connect!
    transport.open()

    ts = client.get_time()
    print('Client Received {}'.format(ts))

    # Close!
    transport.close()


if __name__ == '__main__':
    try:
        main()
    except Thrift.TException as tx:
        print('%s' % tx.message)

简单的 thriftPy 例子

thriftPy似乎比默认的python thrift 库更受欢迎。它也解决了默认的python thrift 库的一些常见问题--这包括用更多的pythonic方法来创建服务器和客户端代码。例如,看看下面的服务器和客户端代码。

服务器代码

import time

import thriftpy
from thriftpy.rpc import make_server

class Dispatcher(object):
    def get_time(self):
        return time.ctime()

time_thrift = thriftpy.load('time_service.thrift', module_name='time_thrift')
server = make_server(time_thrift.TimeService, Dispatcher(), '127.0.0.1', 6000)
server.serve()

客户端代码

import thriftpy
from thriftpy.rpc import make_client

time_thrift = thriftpy.load('time_service.thrift', module_name='time_thrift')
client = make_client(time_thrift.TimeService, '127.0.0.1', 6000)
print(client.get_time())

优缺点

优点:

  • Thrift支持容器类型list、set和map。也支持常量。这是protocol Buffers 所不支持的。然而,rpyc支持所有的python和python库类型--你甚至可以在RPC调用中发送一个numpy数组。(编辑:proto3也支持这些类型。感谢Barak Michener指出这一点)。)

Cons:

缺点:

  • Python感觉不是Thrift的主要语言。不得不添加sys.path.append('gen-py'),这并不能带来流畅的python体验。
  • 与gRPC相比,文档和在线讨论相对匮乏

RPyC

RPyC 是一个纯粹的 python RPC 框架。它不支持多种语言。如果您的整个代码库都使用 python,那么这将是一个简单而灵活的框架。

简单的 rpyc 示例

server.py

import time

from rpyc import Service
from rpyc.utils.server import ThreadedServer

# 定义 TimeService 类
class TimeService(Service):
    def exposed_get_time(self): # 在RPC 调用 名字加 exposed_ 前缀
        return time.ctime()


if __name__ == '__main__':
    s = ThreadedServer(TimeService, port=18871) # 启动服务
    s.start()

下面是注释的服务器代码:

client.py

import rpyc

conn = rpyc.connect('localhost', 18871) # 连接服务
print('Time is {}'.format(conn.root.get_time()))

附加注释的客户端代码:

优缺点

优点:

  • 可能是最容易开始的,不需要理解Protocol Buffer或Thrift的语法
  • 极为灵活。不需要正式使用IDL(接口定义语言)来定义客户-服务器接口。只需开始实现你的代码--它拥抱了python的Duck Typing。

缺点:

  • 缺少多种客户机语言
  • 如果代码库变得足够大,缺乏正式定义的服务接口可能会导致维护问题

gRPC vs Thrift vs RPyC 比较

在深入讨论每个框架的细节之前,让我在这里总结一下。

gRPC

上表的注释:

  • 我发现要让基本的Thrift例子工作起来比较困难。我发现的几个python例子都是针对较早的thrift版本(和python2)。
  • 我对 "可维护性 "的看法是基于这样一个事实:RPyC没有IDL(gRPC使用protobuf,Thrift使用Thrift IDL)--它拥抱鸭子的类型。虽然这使得它非常容易上手,但在维护方面,它可能是一件坏事。

我的偏好是:

  • 如果Python是我要使用的唯一语言,我个人更倾向于使用RPyC。
  • 如果我的服务需要稳健性、可靠性和可扩展性,我更愿意使用gPRC。
  • Thrift最好的一点是它支持更多语言。如果这是你的目标,就选择Thirft吧。

其他要注意的重要事项:

  1. 我没有比较速度,对于某些人来说,这可能是最相关的指标
  2. 我没有处理非常大的服务的经验。我不是评论每个框架的可维护性的合适人选。然而,这是决定选择哪种RPC框架的一个重要标准。

你可以在这个代码库中找到上面例子的代码。

参考链接:

  • 原文连接:https://www.hardikp.com/2018/07/28/services/
  • 代码库:https://github.com/hardikp/service_demo
  • https://thrift.apache.org/
  • https://rpyc.readthedocs.io/en/latest/
  • https://grpc.io/
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 四月 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • gRPC
    • 简单的 gRPC 示例
      • 更多细节
        • 优缺点
          • 简单的thrift例子
            • 优缺点
            • RPyC
              • 简单的 rpyc 示例
                • server.py
                • client.py
              • 优缺点
              • gRPC vs Thrift vs RPyC 比较
              相关产品与服务
              云数据库 MySQL
              腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档