grpc:google 官方的 rpc 框架

2,845 阅读4分钟

protobuf是在项目中经常会用到的一个库,它提供了方便的工具和接口,可以对结构化数据进行序列化和反序列化,便于网络传输。

其实,如果将一个函数调用用结构化数据表示出来,利用protobuf序列化后通过网络传递到远端,在远端进行反序列化解析,就自然地实现了rpc(Remote Procedure Call)的功能。

protobuf中保留了关键字rpc,并且提供了一个RpcChannel的类,供开发者自己实现rpc框架。实现这个rpc框架,其实主要是实现RpcChannel::CallMethod这个接口。我们自己的项目中,就使用了一套自己实现的基于ansyncore的RpcChannel,而某度最近也开源了其基于protobuf的rpc框架,网络部分是使用的Boost::Asio,有兴趣的读者可以自行前往其github wiki页面学习。

那grpc呢,则是google自己基于protobuf(也是google自己开发的库)实现的一套rpc框架。这里使用一张官网的图,表示下其基础架构:

grpc框架

一个简单的例子

可能光说不练有点抽象,那么下面用一个例子说明下grpc的基本用法。

说到网络相关的例子,简单而又实用的当属EchoServer了。

定义一个EchoServer只需一个接口Echo,它接受一条字符串消息,并原样返回一条字符串消息。因此,echo_server.proto文件定义如下:

syntax = "proto3";

package echo_server;

service EchoServer
{
    rpc Echo (EchoRequest) returns (EchoReply) {}
}

message EchoRequest
{
    string msg = 1;
}

message EchoReply
{
    string msg = 1;
}

首先,grpc使用protobuf3.x版本,因此需要在开头声明syntax=”proto3”,剩下的部分和c语言的语法很类似,基本上有了例子之后,照猫画虎很容易就可以写出来自己需要的proto文件。

有了proto文件之后,需要使用protoc将其编译生成对应的py文件。这里grpc提供了一个grpc_tools的库,可以将这一过程程序化:

from grpc.tools import protoc

protoc.main(
    (
        '',
        '-I.',
        '--python_out=.',
        '--grpc_python_out=.',
        './echo_server.proto',
    )
)

生成的echo_server_pb2.py文件中,就定义了我们实现这个EchoServer所需的Servicer类和Stub类。

Server

先看server的实现。

首先,需要定义一个类,继承自xxxServicer(这里是EchoServerServicer),并重写其Echo方法。

class EchoServer(echo_server_pb2.EchoServerServicer):
    def Echo(self, request, context):
        return echo_server_pb2.EchoReply(msg='echo:%s' % request.msg)

可以看到,其中的request和return值,都是按照我们在proto文件中的定义生成的python类型,非常直观。

如何将这个EchoServer类和rpc服务绑定在一起,也是有套路的:

server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
echo_server_pb2.add_EchoServerServicer_to_server(EchoServer(), server)
server.add_insecure_port('[::]:50015')
server.start()
try:
    while True:
        time.sleep(60*60)
except KeyboardInterrupt:
    server.stop(0)

由于server.start()是一个非阻塞式调用,因此需要在后面用一个死循环来防止程序终止/GC导致rpc服务不可用。

Client

Client的实现就更简单了,只需通过ip和port创建一个channel,然后利用这个channel创建一个本地的Stub,然后就可以直接Stub.Echo调用远端的Echo方法了,Stub会帮你处理好一切其他事务(构造调用的结构化数据,序列化,网络传输等等)。

from __future__ import print_function
import grpc
import echo_server_pb2
import sys

if sys.version_info.major == 3:
    raw_input = input
else:
    raw_input = raw_input


def run():
    channel = grpc.insecure_channel('localhost:50015')
    stub = echo_server_pb2.EchoServerStub(channel)
    while True:
        msg = raw_input('you say:')
        reply = stub.Echo(echo_server_pb2.EchoRequest(msg=msg))
        print(reply.msg)
    pass

if __name__ == "__main__":
    run()

这段代码中对print和raw_input这两个py2和py3不兼容的调用打了Monkey Patch,从而使得这段程序可以同时运行在py2和py3的环境中~

总结

至此,grpc的最基础的应用就说的差不多了。除了上面的阻塞式调用,grpc还提供了非阻塞式调用(future)。另外,对于传递的参数和返回值,grpc还支持流式参数(stream、yield)。具体的相关例子,有兴趣的读者可以前往grpc的官网查询。

接下来,我将会使用grpc做一个message hub的应用,功能上应该可以替代目前项目中使用的hub(性能上就不指望替代了,毕竟当前hub使用c++实现的),当作一个练手的实际项目。敬请期待^_^

完整代码详见grpc

转载请注明出处: blog.guoyb.com/2016/10/15/…

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
后端技术小黑屋