分布式系统的核心是分布式通信,而传统上开发一套支持上千台规模集群,可靠性非常高的分布式通信框架,需要不少的精力投入。而在多数情景下,我们(特别是时间宝贵的OP)并不是非常关注技术实现的细节,而是希望有一套成熟、轻量、可靠性高、使用方便而且易于调试的分布式通信框架,可以直接使用,从而把时间放在具体业务逻辑上。
在PyCon 2012大会上,dotcloud公司开源了一套基于ZeroMQ和MessagePack的分布式通信框架(或者说是协议+Python实现)。该框架因为基于ZeroMQ,使用方法是RPC,所以被命名为ZeroRPC。ZeroRPC的特点在其官网的介绍中一目了然[1]:
ZeroRPC is a light-weight, reliable and language-agnostic library for distributed communication between server-side processes.
ZeroRPC的设计初衷是『simple tool to solve a simple problem』,dotcloud工程师在PPT中提到了他们设计目标[2]:
- 最小的使用成本。比如单机时我们这么用
import foo; foo.bar(42)
,分布式后我们希望可以这么用:foo=RemoteService(...) ; foo.bar(42)
- 自文档。不需要看远程服务的源代码,我们能够获得远程服务所支持的方法,docstring等等。
- 优雅的处理异常。调用远程方法产生的异常,不应该中断远程服务,而应该在本地抛出。(异常比返回状态更方便,更简洁)
- 语言无关。同一套协议有多种实现(目前有Python和Node.js两种实现),而且实现简单,目前Python实现只有2000行代码。
- 高可用性,快速。目前Python实现基于gevent+zmq,性能非常高。
- 良好的debug和profile工具
接下来我们以0.4版的Python实现为例,由浅入深的介绍ZeroRPC框架。
pip install zerorpc
接下来我们用一批例子来展示ZeroRPC的基本使用方法。
Server:
import zerorpc
class HelloRPC(object):
def hello(self, name):
return "Hello, %s" % name
s = zerorpc.Server(HelloRPC())
s.bind("tcp://0.0.0.0:8282")
s.run()
Client(Python),和调用本地模块一样的方便:
import zerorpc
c = zerorpc.Client()
c.connect("tcp://127.0.0.1:8282")
print c.hello("RPC")
或者用CLI(debug用):
$ zerorpc tcp://127.0.0.1:8282 hello RPC
connecting to "tcp://127.0.0.1:8282"
'Hello, RPC'
用cli可以列出远程模块所支持的方法,并可以查看docstring文档:
# 将urllib模块发布
$ zerorpc --server --bind tcp://127.0.0.1:1234 urllib
# 列出远程所有以q开头的方法
$ zerorpc tcp://127.0.0.1:1234 | grep ^q
quote quote('abc def') -> 'abc%20def'
quote_plus Quote the query fragment of a URL; replacing ' ' with '+'
# 列出quota_plus的文档
$ zerorpc --inspect tcp://127.0.0.1:1234 quote_plus
connecting to "tcp://127.0.0.1:1234"
quote_plus(s, safe=)
Quote the query fragment of a URL; replacing ' ' with '+'
ZeroRPC可将远程方法抛出的异常传递到本地,这样不仅符合业务逻辑,而且异常捕获也减少了服务端的容错逻辑,提高了稳定性。
$ zerorpc tcp://127.0.0.1:1234 quote_plus
connecting to "tcp://127.0.0.1:1234"
Traceback (most recent call last):
...此处省略
zerorpc.exceptions.RemoteError: Traceback (most recent call last):
...此处省略
TypeError: quote_plus() takes at least 1 argument (0 given)
Streaming Responses 类似于 Python 的生成器,尤其是在大数据量操作时优势明显。比如有一个很长很大的列表L,如果一次传递过去再进行处理,不仅网络传输需要时间,而且占用内存也较大。此时如果使用Streaming,其实是将列表的每个元素分别传递并分别处理返回(迭代器),效率十分高。
# streaming example, 省略不必要的代码
class StreamingRPC(object):
@zerorpc.stream
def streaming_range(self, fr, to, step):
return xrange(fr, to, step)
# client,省略不必要的代码
for item in c.streaming_range(10, 20, 2):
print item
ZeroRPC可以放在Proxy之后,实现高可用性,以HAProxy为例:
启动HAProxy,使其工作在TCP模式,并将请求转发给后端:
$ cat haproxy.cfg
listen zerorpc 0.0.0.0:1111
mode tcp
server backend_a localhost:2222 check
server backend_b localhost:3333 check
$ haproxy -f haproxy.cfg
启动一个或多个后端:
$ zerorpc --server --bind tcp://0.0.0.0:2222 urllib
此时便可用client去连接HAProxy, HAProxy会将请求转发给可用的后端。
$ zerorpc tcp://localhost:1111
ZeroMQ[4]是一种基于消息队列的多线程网络库,其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象,提供跨越多种传输协议的套接字。ZeroMQ是网络通信中新的一层,介于应用层和传输层之间(按照TCP/IP划分),其是一个可伸缩层,可并行运行,分散在分布式系统间。
简单的说,ZMQ提供了完全不同于传统BSD Socket的网络通信接口,屏蔽了Socket的处理细节,并提供了几种通信模型,基本覆盖了分布式系统的需求。
ZMQ提供了以下几种通信模型,而这些通信模型也同样可在ZeroRPC中使用:
REQ-REP
: Request-Reply 模型,类似于传统的通信模型,但ZMQ的Request端和Reply端无论谁先启动均可以,只是请求必须遵循Request-Reply的顺序。PUB-SUB
: Publish-Subscribe 模型,Publish节点为信息源,Subscribe节点接收信息。如果Subscribe节点中途退出,不影响信息的继续发布。PUSH/PULL
: PipeLine 模型,以扇入扇出的模式连接节点,多用于任务发布和信息收集的场景。
ZeroRPC选择MessagePack[5]作为序列化工具,相比于JSON/BSON/YAML等格式,MsgPack提供20~50倍的序列化速度以及1/2大小的序列化产出。
完成的协议参见文档[3],简略说,ZeroRPC分为三层:
- Wire (or transport) layer 利用ZeroMQ和MessagePack进行通信
- Event (or message) layer 最复杂的一层,处理心跳,事件等
- RPC layer RPC Request+Response
由于ZMQ隐藏了Socket细节,所以无法感知断线,于是ZeroRPC采用心跳包的方式进行在线检测。Client端在断线后会抛出LostRemote异常。默认断线超时为30s,可设定。
目前版本的ZeroRPC没有实现任何形式的身份验证,传输加密官方建议通过SSL实现。
最新版本的ZeroRPC支持middleware API,比如StatsD Middleware
[6],可以跟踪Request/Response的耗时并传送给StatsD,以分析系统性能。
https://github.com/dotcloud/zerorpc-python
由于文档较缺失,如果需要了解的更深,建议直接阅读源代码,2000行不到,短小精悍。
Good job