新世纪通讯函式库 – ZeroMQ

来源:互联网 发布:淘宝店铺解封需要钱吗 编辑:程序博客网 时间:2024/05/01 13:23

花了一些时间把原版的繁体转化为了简体,修改了一下常用语。


    2012年就快到了,离2015年第三次冲击也快到了,听不懂我在说什么就算了,跟主题没什么关联,在介绍主角之前,我总喜欢描述一下主角的背景。

欲上云端,必先分散

    这几年,云端炒得正热门,爱跟风的台湾人当然也不能错过,不管是卖主机的声称他们是做云端的,连卖房子的也称他们的是云端,哪天就连在路边看见云端鸡排都请不要大惊小怪,因为那简直和奈米光触媒鸡排一样有异曲同工之妙,然而口口声声说云端,但前阵子iPhone4S预购就给了各家电信商一个大巴掌,他们的网页服务器都在一瞬间被大量拥入的使用者塞暴了,更别说其中还有不乏自家推出云端服务的电信商,说真的,连分散运算的基本功都做不到,跟人家云什么端? 你说是吧?

    但今天的主角不在于云端是什么,或是什么才算真正的云端之类的无聊话题,今天的主轴是分散式运算,因为要达到规模的运算和高可得性就一定要扯到分散式的运算,而分散式运算一直以来都不是什么简单的议题,最麻烦的就要算是沟通上的问题了,如果你有 三个节点之间想要进行沟通,该怎么做? 你说简单,直接串在一起不就好了?
这里写图片描述
    好吧,或许你只有固定的三个节点,这好办,但是要是你有更多的节点呢? 考虑一下十个节点、一百个节点,光想像就能知道,随著节点的增加,他们之间的连线数量呈指数在成长,不光是连线本身的问题,连这些节点要如何知道对方的存在以及管理都是相当麻烦的问题。
这里写图片描述

中央集权

    光想就令人头皮发麻,为了解决这样的问题,一个经典的模式被设计且大量使用,那就是所谓的 Broker (中介),也有称Message Queue、Message bus等,简单的概念,就是所有节点都连向中间的信息交换服务器,而沟通都透过这个中央的交换中心来进行,节点与节点之间无需去在意到底谁在和我沟通,只需要在意讯息的种类和内容即可。
这里写图片描述
    虽然这样的模式解决了一些问题,但同时也引入了新的问题,分散式系统的头号公敌 – 单点失效,正因为所有节点都依赖著这个中间的中介帮忙转送讯息,这也意味著当中介网路断线、当机等等意外的发生,都会让整个系统陷入停摆(注:奔溃)的状态,除此之外还有另一个严重的问题,就是当节点数量增加,中介的工作量也会一直往上升,在无法扩增的情况下会造成整个系统的扩展性受到限制,效能也会因为中介而受到限制。

各自为政

    正因为中央集权造成了问题,所以有人提出了各自为政来解决问题,可以想见的,在分散式的系统里,并不是所有的节点都需要和所有人进行沟通,他们通常只需要和特定的节点沟通,举个例子,假设你设计的是一个多媒体档案的处理系统,在第一个节点可能做的是Hash,用来产生该档案的唯一识别编号,节点二做的是转档,节点三做的是储存,节点四做的是归档,那么你需要的就只是这样的结构,如果以物件导向的设计模式来看,我们称这样的结构为责任链
这里写图片描述
    再看另一个例子,如果我们是气象局,想发布各种天气的消息,那么你需要的是一个服务器,让大家去订阅他们有兴趣的主题,这样的结构以物件导向的观点来看就叫做观察者模式,与我们先前见到的Broker做的是一样的事情,然而在这里的重点在于该服务器只负责发布天气消息,并不参与讯息的交换。
这里写图片描述
    整体的概念就是各自有不同的子系统,我们透过通讯的方式将它们串在一起,这样做有个好处就是效能好,再来就是设计得当的话,某子系统虽然停摆,但不会影响到所有的系统。

各自为政的代价

    虽然各自为政的做法有其好处,但相对的也有它的代价,光是通讯的协定就是很恼人的问题,考虑一下各种不同的子系统间该用什么协定来沟通,连线中断了怎么处理,负载平衡? 这些问题都要花相当大的心力来解决。

    而在不同的环境下,使用不同的通讯方式都各有好处,使用TCP/IP的话,不管你的节点在同一台机器或是远端,都可以连线,缺点是在同一台机器会有一定的效能耗损,而且遇到广播的讯息,同样的讯息被传送N次,就没有UDP广播来得划算,如果节点是在同一台机器上沟通,使用IPC(注:进程间通讯)的方式效率好,如果是在同一个thread里,那么共享内存的方式最快,IPC反而会拖慢速度,正因为有各种不同的考量,使得想要达成高效率的分散式系统是一件困难的事情。

是主角登场的时候了 – ZeroMQ

这里写图片描述
    讲了半天,ZeroMQ到底是做什么用的? 简单的来说,它是一套网路通讯函式库,用来解决上面所提到的问题,考虑一下上面所提到的物件导向设计模式,你想,即然那样的模式一再出现,为什么我们不能将这些常见的通讯方式变成可以轻易重覆使用的形式? ZeroMQ所做到的即是如此,它将常见的通讯方式定义成不同行为的socket,让你可以轻易地重覆使用这些样式去组出强健的分散式系统。

老样子 Hello world.. 不! 是Hello baby

    虽然大部份程式范例都喜欢用Hello world,但我不喜欢和他们一样,我喜欢Hello baby,我们来看一下简单的Hello baby范例。

Client端

import zmqcontext = zmq.Context()socket = context.socket(zmq.REQ)socket.connect ("tcp://127.0.0.1:7788")socket.send('hello')print socket.recv()

Server端

import zmqcontext = zmq.Context()socket = context.socket(zmq.REP)socket.bind ("tcp://*:7788")print socket.recv()socket.send('baby')

    有了这两个Python程式,你可以在本机执行,不管Client或Server端先执行都可,你可能觉得我在说笑,哪有Client先执行的道理,接着会解释,他们输出的结果会像这样:

Client端:

baby

Server端:

hello

    所以这之间到底发生了什么事? 很简单的在Client方,以TCP的方式连接了本机端的7788 port,接著送了一个request,然后接收response然后印出来,而Server方以TCP的方式绑定了本地端的7788 port,然后读了一个request后送了一个response回去,这有什么特别? 目前为止 … 没有,但是,让我们看下去。

ZeroMQ的特异功能

    上面的两个程式整体看起来和一般的socket没两样,而行为也没太大差别,所以它的特异功能到底在哪里? 难不成它可以用手指读封包吗? 事实上它的特别之处可多了,这两个程式并没有表现出太特别的地方,我们改写上面的程式让它变成更能突显ZeroMQ特色的程式。

req.py

import zmqimport randomimport timecontext = zmq.Context()socket = context.socket(zmq.REQ)socket.bind("tcp://*:7788")# wait all worker connectedtime.sleep(1)for i in range(9):    a = random.randint(0, 100)    b = random.randint(0, 100)    print 'Compute %s + %s ...' % (a, b)    # send request to peer    socket.send_multipart([str(a), str(b)])    # receive response from peer    rep = socket.recv()    print ' =', rep

rep.py

import osimport zmqcontext = zmq.Context()socket = context.socket(zmq.REP)socket.connect("tcp://localhost:7788")print 'Worker %s is running ...' % os.getpid()while True:    # receive request    a, b = socket.recv_multipart()    a = int(a)    b = int(b)    print 'Compute %s + %s and send response' % (a, b)    socket.send(str(a + b))

接著我们先执行三个rep.py,然后再执行一个req.py,看发生了什么事

req.py

Compute 43 + 91 ... = 134Compute 21 + 63 ... = 84Compute 17 + 93 ... = 110Compute 29 + 98 ... = 127Compute 90 + 55 ... = 145Compute 14 + 74 ... = 88Compute 3 + 85 ... = 88Compute 12 + 73 ... = 85Compute 73 + 21 ... = 94

rep.py 1

Worker 7296 is running ...Compute 43 + 91 and send responseCompute 29 + 98 and send responseCompute 3 + 85 and send response

rep.py 2

Worker 6532 is running ...Compute 21 + 63 and send responseCompute 90 + 55 and send responseCompute 12 + 73 and send response

rep.py 3

Worker 5928 is running ...Compute 17 + 93 and send responseCompute 14 + 74 and send responseCompute 73 + 21 and send response

    这两个程式做的事情很简单,req.py负责产生两个乱数,将其当作request送给socket的另一端,而rep.py则是接收两个乱数request,算出结果来送回给发出者,但是这好像哪里不太对劲是吗? 三个socket连到一个socket? 为什么送request的一方居然变成bind而不是connect了? 为什么connect的一方先执行居然也能连线? 我已经听见你在电脑前的吼叫,让我们来说明一下这到底是怎么回事。

连线先后顺序无关、自动重连机制

    ZeroMQ的socket有个特色就是对于谁先bind谁后connect之类的完全都不在乎,如果是connect先行,发线连线无法建立,ZeroMQ会自动重试,当bind也确立了,连线就会自动接上了,有了这样的特性,只要管怎样连接和对方的位址是什么即可。

    通常bind那端都是位址为大家所熟知的那端,connect都是位址不为人知的,以我们这样的例子,因为worker可能有很多个,所以我们将REQ端换成bind,而REP端换成connect,方便多个rep.py连接req.py。

    除此之外,REQ和REP两种角色不管哪方bind哪方connect运算的方式都是一样的。

多个Socket连线、多位址绑定

    ZeroMQ的socket之间可以多个互相连线,所以一个socket的另一端可能有N个节点连接,除此之外,同一个socket也可以绑在不同的位址上。

自动负载平衡

    如果你仔细观察上面程式的输出,就会发现request是依序分配给三个rep.py,这也是ZeroMQ的特色之一,REQ端会将send的message用Round-robin的方式分给所有的远端连线,而你有多少个连线,他都会照一样的规则分配。

    但因为先前有提到自动连线,因此会有个问题,当第一个连线接上时,其它连线还来不及连上,此时request可能已经大量分配给第一个连上的,为了解决这问题,我们安排了time.sleep(1)来等所有worker连上线,以避免早起的鸟儿吃光了虫子。

讯息传输

    你可以发现我们送资料和处理的都是讯息,ZeroMQ可以提供你传送多段资料在一笔讯息里,因此我们在这看到的send_multipart和recv_multipart作用即为如此,有了这样的特性,你可以不用担心通讯协定的问题,只管专心处理讯息即可。

支援不同的通讯方式

    看见了我们在程式中所写的 “tcp://*:7788” 和 “tcp://localhost:7788” 吗? 它们暗示了TCP只不过只是支援的其中一种通讯方式而已,ZeroMQ还支援IPC,但目前只支援Linux下的domain socket,例如 “ipc:///tmp/req.socket”,甚至它还支援thread之间的通讯方式,因此如果你的两个节点放在同一个process里,为了效能考量,你可以用这种协定让通讯效率最佳化。

    为了节省传输的封包,ZeroMQ甚至提供了基于UDP的广播通讯方式,因此当你将节点放在同一个网路下,广播的功能就能节省大量的重覆封包传送。

支援N种语言

    虽然我们的范例都是用Python写的,但ZeroMQ目前已经支持了几乎所有你能在台面上看到的主流语言,你喜欢用Lua写子系统A? OK! 你喜欢用Perl写系统B? 可以! C语言? 当然没问题,你喜欢PHP? 厄… 也可以啦,总之,ZeroMQ让你从不同语言的通讯中解藕开来,只要专注在于讯息的处理上即可。

更多的样式

    我们做为范例的REQ/REP只不过是ZeroMQ所提供的样式中的一种,它的特性就是一个request一个response,而REQ端会对所有的连线做fairly queue,也就是会公平地把request塞给REP端,借用官网的图。
这里写图片描述
    另一种常见的样式是PUB/SUB,也就是我们先前提到的观察者模式,它的特色是所有PUB发送的消息会广播给所有SUB的连线,而且SUB可以设定只要某段字串开头的讯息。

这里写图片描述
    还有很常见的需求就是我们想将资料往某个方向负载平衡地推送,这时PUSH/PULL样式就派上用场了,PUSH会将负载分散给PULL端,而且只能由PUSH推往PULL。

这里写图片描述
    还有另一种样式是跟一般socket没两样的一对一连线,叫做PAIR。

这里写图片描述

参在一起做撒尿牛丸

事实上ZeroMQ还有更高级的样式,但在本文就不介绍了,接著要大略介绍的是官网的一个例子,将这些样式组合在一起,官网的例子是说当你需要分散式的Key/Value形式的服务器,利用ZeroMQ来达成其组合方式就像这样。
这里写图片描述

    服务器端负责储存一份快取,且会透过PUB/SUB让client保持更新,而Client端被更动时,透过PUSH丢给服务器,如果新的Client上线了,就透过REQ向server要最新的一份快取。

    这样就是用ZeroMQ实作分散式系统的一个简易的实例,事实上它能做远比这个还要更复杂的东西。

自从用了ZeroMQ,我人也高了,头也壮了,考试都得一百分呢

    有了ZeroMQ,分散式系统从又难又脑人变成很难而已,因为通讯的部份由ZeroMQ来完成了,剩下的你只要专心来考虑节点之间的拓扑与连接方式、通讯方式,以及讯息的处理,不同语言的支持也让你不再担心要用什么语言来实作,或是你的同事懂不懂某种语言,真正的心力被花费在设计上,分散式系统因此也能更轻易地设计与实作,而ZeroMQ的本意是用于即时地处理大量的金融资料,效率更是ZeroMQ的金字招牌之一,如果你需要复杂且高效的分散式系统,ZeroMQ绝对是你的好朋友。

    最后,值得一提的是有人基于ZeroMQ的特性设计了与程式语言无关的网页框架,或著更精确的来说是与服务器沟通的协定,类似CGI或WSGI那样,但是透过ZeroMQ因此和语言无关,叫Mongrel,目前出到第二版,有兴趣可以研究看看,或许哪天也会写篇文章来介绍。

原文地址:
http://blog.ez2learn.com/2011/12/31/transport-lib-of-new-era-zeromq/

0 0
原创粉丝点击