zeromq的工作原理及使用
zeromq的工作原理及使用
一、ZeroMQ使用
1.1ZeroMQ概述
ZeroMQ是一种基于消息队列的多线程网络库,其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象,提供跨越多种传输协议的套接字。ZeroMQ是网络通信中新的一层,介于应用层和传输层之间(按照TCP/IP划分),其是一个可伸缩层,可并行运行,分散在分布式系统间。
ZeroMQ看起来想一个可嵌入的网络库,但其作用就像是一个并发框架。它为你提供了各种传输工具,如进程内,进程间,TCP和组播中进行原子消息传递的套接字。你可以使用各种模式实现N对N的套接字连接,这些模式包括发布订阅,请求应答,扇出模式,管道模式。它的速度足够快,因此可以充当集群产品的结构,他的异步IO模型提供了可扩展的多核应用程序,用异步消息来处理任务。它虽然是以C为源码进行开发,但是可以绑定多种语言。
1.2请求/应答模式
说到“请求-应答”模式,不得不说的就是它的消息流动模型以及数据包装模型。
消息流动模型指的是该模式下,必须严格遵守“一问一答”的方式。发出消息后,若没有收到回复,再发出第二条消息时就会抛出异常。同样的,对于Rep也是,在没有接收到消息前,不允许发出消息。基于此构成“一问一答”的响应模式。
1.2.1服务端
import time
import zmq
context=zmq.Context()
socket=context.socket(zmq.REP)
socket.bind("tcp://*:5555")
while True:
#Wait for next request from client
message=socket.recv()
print("Received request:%s"%message)
#Do some'work'
time.sleep(1)
#Send reply back to client
socket.send(b"World")
1.2.2客户端
import zmq
context=zmq.Context()
#Socket to talk to server
print("Connecting to hello world server…")
socket=context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")
#Do10requests,waiting each time for a response
for request in range(10):
print("Sending request%s…"%request)
socket.send(b"Hello")
#Get the reply.
message=socket.recv()
print("Received reply%s[%s]"%(request,message))
1.3发布/订阅模式
“发布-订阅”模式下,“发布者”绑定一个指定的地址,例如“192.168.55.210:5556”,“订阅者”连接到该地址。该模式下消息流是单向的,只允许从“发布者”流向“订阅者”。且“发布者”只管发消息,不理会是否存在“订阅者”。上图只是“发布-订阅”的最基本的模型,一个“发布者”可以拥有多个订阅者,同样的,一个“订阅者”也可订阅多个发布者。下面给出“发布-订阅”模型的样例程序:
1.3.1发布者
import zmq
from random import randrange
context=zmq.Context()
socket=context.socket(zmq.PUB)
socket.bind("tcp://*:5556")
while True:
zipcode=randrange(1,100000)
temperature=randrange(-80,135)
relhumidity=randrange(10,60)
socket.send_string("%i%i%i"%(zipcode,temperature, relhumidity))
1.3.2订阅者
import sys
import zmq
#Socket to talk to server
context=zmq.Context()
socket=context.socket(zmq.SUB)
print("Collecting updates from weather server…") socket.connect("tcp://localhost:5556")
#Subscribe to zipcode,default is NYC,10001
zip_filter=sys.argv[1]if len(sys.argv)>1else"10001"
#Python2-ascii bytes to unicode str
if isinstance(zip_filter,bytes):
zip_filter=zip_filter.decode('ascii')
socket.setsockopt_string(zmq.SUBSCRIBE,zip_filter)
#Process5updates
total_temp=0
for update_nbr in range(5):
string=socket.recv_string()
zipcode,temperature,relhumidity=string.split() total_temp+=int(temperature)
print("Average temperature for zipcode'%s'was%d F"%( zip_filter,total_temp/(update_nbr+1))
)
1.4管道模式
“管道模式”一般用于任务分发与结果收集,由一个任务发生器来产生任务,“公平”的派发到其管辖下的所有worker,完成后再由结果收集器来回收任务的执行结果。
注:在ZeroMQ中并没有绝对的服务端与客户端之分,所有的数据接收与发送都是以连接为单位的,只区分ZeroMQ定义的类型。就像套接字绑定地址时,可以使用“bind”,也可以使用“connect”,只是通常我们将理解中的服务端“bind”到一个地址,而理解中的客户端“connec”到该地址。
1.4.1任务发生器
import zmq
import random
import time
try:
raw_input
except NameError:
#Python3
raw_input=input
context=zmq.Context()
#Socket to send messages on
sender=context.socket(zmq.PUSH)
sender.bind("tcp://*:5557")
#Socket with direct access to the sink:used to syncronize start of batch
sink=context.socket(zmq.PUSH)
sink.connect("tcp://localhost:5558")
print("Press Enter when the workers are ready:")
_=raw_input()
print("Sending tasks to workers…")
#The first message is"0"and signals start of batch
sink.send(b'0')
#Initialize random number generator
random.seed()
#Send100tasks
total_msec=0
for task_nbr in range(100):
#Random workload from1to100msecs
workload=random.randint(1,100)
total_msec+=workload
sender.send_string(u'%i'%workload)
print("Total expected cost:%s msec"%total_msec)
#Give0MQ time to deliver
time.sleep(1)
1.4.2Worker
import sys
import time
import zmq
context=zmq.Context()
#Socket to receive messages on
receiver=context.socket(zmq.PULL) receiver.connect("tcp://localhost:5557")
#Socket to send messages to
sender=context.socket(zmq.PUSH) sender.connect("tcp://localhost:5558")
#Process tasks forever
while True:
s=receiver.recv()
#Simple progress indicator for the viewer sys.stdout.write('.')
sys.stdout.flush()
#Do the work
time.sleep(int(s)*0.001)
#Send results to sink
sender.send(b'')
1.4.3结果收集器
import sys
import time
import zmq
context=zmq.Context()
#Socket to receive messages on
receiver=context.socket(zmq.PULL) receiver.bind("tcp://*:5558")
#Wait for start of batch
s=receiver.recv()
#Start our clock now
tstart=time.time()
#Process100confirmations
for task_nbr in range(100):
s=receiver.recv()
if task_nbr%10==0:
sys.stdout.write(':')
else:
sys.stdout.write('.')
sys.stdout.flush()
#Calculate and report duration of batch
tend=time.time()
print("Total elapsed time:%d msec"%((tend-tstart)*1000))
二、ZeroMQ特点
2.1嵌入式消息组件
与rabbitMQ,ActiveMQ有很大的不同,如果说rabbitMQ已经近乎是一个小型操作系统,那么ZeroMQ 就像是一个嵌入在操作系统内的一个组件,说白了ZeroMQ就是一组jar包,直接嵌入到项目中就可以运行,它不需要一台独立的服务器来承载整个消息系统。
ZeroMQ关注的不是消息的可靠送达,而是着眼于端到端的发送,接收…它希望的是尽快完成任务,而不介意部分消息的丢失。
但这也并不是说他完全没有持久化的功能,ZeroMQ是具有一定的本地持久化的功能的,但是能保存的数据量比较有限,而且是暂存于内存中的。
2.2高的离谱的吞吐量
下图是网上找到的一张关于MQ的性能分析的图表,显示的是每秒钟发送和接受的消息数。整个过程共产生1百万条1K的消息,测试环境为Windows Vista。从测试数据可以看出,ZeroMQ的性能远远高于其它3个MQ。或者说ZeroMQ与其他3各MQ根本就不再一个量级上比较合适。
至于这样的原因跟ZeroMQ的定位,以及对消息的处理方式有很大关联。
ZeroMQ对于消息的处理可以说除却请求-应答模式之外,基本就是不关系消息是否丢失,它只管发送。ZeroMQ的定位,它的创始人一直在其社区表示,团队将立志于把ZeroMQ融入到Linux内核中去。
基于以上两点,高效的处理速度就成了它必不可少的特点之一。
2.3无锁队列
通过线程间数据流动来保证同一时刻任何数据都只会被一个线程持有,以此实现多线程的“去锁化”。
2.4多核下的线程绑定
传统的多线程并发模式一般会采用锁,临界区,信号量等技术来控制,而ZeroMQ给出的建议是:在创建IO时不要超出CPU核数。
当我们创建一个上下文时都会有这么一句代码“Context context=ZMQ.context(1);”这里就指定了IO 线程数。通常来说一个线程足矣。但是如果希望创建多个IO线程,最好不要超出CPU核数,因为此时ZeroMQ会将工作线程其实就是那个Poller绑定到每一个核,免除了线程上下文切换带来的开销。
三、总结
首先就扩展性而言,那毫无疑问的是ZeroMQ最强,其余其中MQ都已经是成形的产品,已经是一款应用程序了。而ZeroMQ说白了就是一组库函数。基于这种情况,我们可以按自己的需要来开发适合自己的Socket组件,至于它对于消息持久化的不支持,只是原生不支持,因为它的定位不是保证可靠的消息传输。所以在可靠性这部分我们完全可以按自己的需求进行扩展。一组lib的扩展度明显是宽于产品级的rabbitMQ之类的产品。
技术上虽然ZeroMQ立志于成为Linux内核的消息组件,但是不得不说它的开源社区活跃度是远远不及RabbitMQ或者ActiveMQ。或许是处于它的可靠性考虑,它的应用场景比较受限制。
可靠性上虽然ActiveMQ也具备,只是性能上相比于RabbitMQ还是有一定差距,所以大部分的MQ 选型都是RabbitMQ。