grpc:google 官方的 rpc 框架
WesFiedler
8年前
<p>protobuf是在项目中经常会用到的一个库,它提供了方便的工具和接口,可以对结构化数据进行序列化和反序列化,便于网络传输。</p> <p>其实,如果将一个函数调用用结构化数据表示出来,利用protobuf序列化后通过网络传递到远端,在远端进行反序列化解析,就自然地实现了rpc(Remote Procedure Call)的功能。</p> <p>protobuf中保留了关键字rpc,并且提供了一个RpcChannel的类,供开发者自己实现rpc框架。实现这个rpc框架,其实主要是实现RpcChannel::CallMethod这个接口。我们自己的项目中,就使用了一套自己实现的基于ansyncore的RpcChannel,而某度最近也开源了其基于protobuf的 <a href="/misc/goto?guid=4959716956567576429" rel="nofollow,noindex">rpc框架</a> ,网络部分是使用的Boost::Asio,有兴趣的读者可以自行前往其github wiki页面学习。</p> <p>那grpc呢,则是google自己基于protobuf(也是google自己开发的库)实现的一套rpc框架。这里使用一张官网的图,表示下其基础架构:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/749fd5af5a72779463f3f0f467152912.jpg"></p> <h2><strong>一个简单的例子</strong></h2> <p>可能光说不练有点抽象,那么下面用一个例子说明下grpc的基本用法。</p> <p>说到网络相关的例子,简单而又实用的当属EchoServer了。</p> <p>定义一个EchoServer只需一个接口Echo,它接受一条字符串消息,并原样返回一条字符串消息。因此,echo_server.proto文件定义如下:</p> <pre> <code class="language-python">syntax = "proto3"; package echo_server; service EchoServer { rpc Echo (EchoRequest) returns (EchoReply) {} } message EchoRequest { string msg = 1; } message EchoReply { string msg = 1; } </code></pre> <p>首先,grpc使用protobuf3.x版本,因此需要在开头声明syntax=”proto3”,剩下的部分和c语言的语法很类似,基本上有了例子之后,照猫画虎很容易就可以写出来自己需要的proto文件。</p> <p>有了proto文件之后,需要使用protoc将其编译生成对应的py文件。这里grpc提供了一个grpc_tools的库,可以将这一过程程序化:</p> <pre> <code class="language-python">from grpc.tools import protoc protoc.main( ( '', '-I.', '--python_out=.', '--grpc_python_out=.', './echo_server.proto', ) ) </code></pre> <p>生成的echo_server_pb2.py文件中,就定义了我们实现这个EchoServer所需的Servicer类和Stub类。</p> <h3><strong>Server</strong></h3> <p>先看server的实现。</p> <p>首先,需要定义一个类,继承自xxxServicer(这里是EchoServerServicer),并重写其Echo方法。</p> <pre> <code class="language-python">class EchoServer(echo_server_pb2.EchoServerServicer): def Echo(self, request, context): return echo_server_pb2.EchoReply(msg='echo:%s' % request.msg) </code></pre> <p>可以看到,其中的request和return值,都是按照我们在proto文件中的定义生成的python类型,非常直观。</p> <p>如何将这个EchoServer类和rpc服务绑定在一起,也是有套路的:</p> <pre> <code class="language-python">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) </code></pre> <p>由于server.start()是一个非阻塞式调用,因此需要在后面用一个死循环来防止程序终止/GC导致rpc服务不可用。</p> <h3><strong>Client</strong></h3> <p>Client的实现就更简单了,只需通过ip和port创建一个channel,然后利用这个channel创建一个本地的Stub,然后就可以直接Stub.Echo调用远端的Echo方法了,Stub会帮你处理好一切其他事务(构造调用的结构化数据,序列化,网络传输等等)。</p> <pre> <code class="language-python">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() </code></pre> <p>这段代码中对print和raw_input这两个py2和py3不兼容的调用打了Monkey Patch,从而使得这段程序可以同时运行在py2和py3的环境中~</p> <h2><strong>总结</strong></h2> <p>至此,grpc的最基础的应用就说的差不多了。除了上面的阻塞式调用,grpc还提供了非阻塞式调用(future)。另外,对于传递的参数和返回值,grpc还支持流式参数(stream、yield)。具体的相关例子,有兴趣的读者可以前往grpc的官网查询。</p> <p>接下来,我将会使用grpc做一个message hub的应用,功能上应该可以替代目前项目中使用的hub(性能上就不指望替代了,毕竟当前hub使用c++实现的),当作一个练手的实际项目。</p> <p> </p> <p> </p> <p>来自:http://blog.guoyb.com/2016/10/15/grpc/</p> <p> </p>