原文地址:https://www.hardikp.com/2018/07/28/services/
那一年是2015年。我正在写一堆ML训练脚本以及几个生产脚本。他们都需要金融数据。数据分散在多个表和多个数据存储中。日内市场数据以不同方式存储在cassandra集群中,而每日/每月的数据则在MySQL数据库中。同样地,不同类型的证券(期货、期权、股票等)被存储在不同的位置。
所以,我决定写一个可以在我的脚本中使用的数据操作库。结果这个数据操作库在我的团队中相当受欢迎。它拥有我们当时需要的所有东西:
然而,它有一些我当时无法预见的致命的缺陷。随着时间的推移,依赖这个库的生产脚本的数量成倍增长。而我们的数据操作库直接调用数据库查询。
大约一年前,有人问我,我们是否应该把那个代码库转换为服务。我对此置之不理--完全没有意识到在接下来的一年里我将面临的问题。说实话,那时候我还没有完全理解服务或微服务--这让我对它用于数据获取这样的事情持怀疑态度。我仍然相信,将这些代码作为一个库是灵活性和快速变化的保证。
但是,几天前我终于开始重新审视这些服务。在过去的几天里,我看了gRPC、Thrift和RPyC。我在这篇文章中总结了我的初步结论。因为我主要是用python来做所有事情,所以我是从这个角度来看待这些框架的。
您可以在这个链接中找到后续示例的代码。
gGPC使用Protocal Buffers 进行序列化和反序列化。它是由谷歌开发的--他们在重写内部框架stubby的时候将其作为一个开源软件发布。目前,包括Netflix和Square在内的一些公司正在使用这个框架来实现他们的服务。
让我们直接跳到最简单的例子中。
我们将为所有3个框架使用相同的玩具示例:
创建一个 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:
rpc GetTime (TimeRequest) returns (TimeReply) {}
rpc GetTime (TimeRequest) returns (stream TimeReply) {}
rpc GetTime (stream TimeRequest) returns (TimeReply) {}
rpc GetTime (stream TimeRequest) returns (stream TimeReply) {}
带有内置的超时功能,这在实践中相当方便。许多应用程序要求在一定的时间间隔内做出响应。
优点:
缺点:
链接:
Thrift
Thrift在Facebook和Hadoop/Java服务世界中相当流行。它是在Facebook创建的,他们在某个时候把它作为一个Apache项目开源了。
使用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())
优点:
Cons:
缺点:
RPyC 是一个纯粹的 python RPC 框架。它不支持多种语言。如果您的整个代码库都使用 python,那么这将是一个简单而灵活的框架。
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()
下面是注释的服务器代码:
import rpyc
conn = rpyc.connect('localhost', 18871) # 连接服务
print('Time is {}'.format(conn.root.get_time()))
附加注释的客户端代码:
优点:
缺点:
在深入讨论每个框架的细节之前,让我在这里总结一下。
gRPC
上表的注释:
我的偏好是:
其他要注意的重要事项:
你可以在这个代码库中找到上面例子的代码。
参考链接: