Distributed Systems with ZeroMQ

Distributed Systems with ZeroMQ

08.15.2012
| 17492 views |
The Enterprise Integration Zone is presented by DZone and FuseSource and JNBridge.  For open source systems based on Apache Camel, ActiveMQ, or ServiceMix, look into FuseSource's training and technology.  For .NET and Java interoperability, JNBridge has the answers.

Departing a bit from my current series on gevent and Python, today Iwant to take a look at a different networking technology that's been gainingtraction: ZeroMQ. So without further ado, let's jump right in...

ZeroMQ design principles

First, ZeroMQ is not a messagebroker. People sometimes mistake it for one because of its name. Actually, ZeroMQ is a library thatsupports certain network communication patterns using sockets. The "MQ" partcomes in because ZeroMQ uses queues internally to buffer messages so that youdon't block your application when sending data. When you say socket.send(...),ZeroMQ actually enqueues a message to be sent later by a dedicatedcommunication thread. (This communication thread and its state are encapsulated inthe ZeroMQ Context object used below; most programs will have a singleContext.)

ZeroMQ binding/connecting versus "normal" sockets

Next, keep in mind that ZeroMQ separates the notion of clients andservers from the underlying communication pattern. For instance, you may be usedto creating a socket for receiving requests with a pattern similar to thefollowing:

01. from socket import socket
02.  
03. sock = socket()
04. sock.bind(('', 8080))
05. sock.listen(256)
06. while True:
07. cli = sock.accept()
08. # The following code would probably be handled in a 'worker' thread or
09. # greenlet. It's included here only for example purposes.
10. message = cli.recv(...)
11. response = handle_message(message)
12. cli.send(response)
The following code would probably be handled in a 'worker' thread or greenlet. It's included here only for example purposes.

The client would then connect() to the server and send a request:

1. from socket import socket
2.  
3. sock = socket()
4. sock.connect(('localhost', 8080))
5. sock.send(message)
6. response = sock.recv(...)


In ZeroMQ, either end of the request/response pattern can bind, and either endcan connect. For instance, using the pyzmq library, you can have your"server" (the one who handles requests) connect to the "client" (the one whosends requests). The "server" code then looks like this:

01. import zmq
02. context = zmq.Context.instance()
03.  
04. sock = context.socket(zmq.REP)
05. sock.connect('tcp://localhost:8080')
06.  
07. while True:
08. message = sock.recv()
09. response = handle_message(message)
10. sock.send(response)


The "client" code would look like this:

1. import zmq
2. context = zmq.Context.instance()
3.  
4. sock = context.socket(zmq.REQ)
5. sock.bind('tcp://*:8080')
6.  
7. sock.send(message)
8. response = sock.recv()


A couple of things deserve attention here. First, as noted above, the "server" isdoing the connecting, and the "client" is doing the binding. Another thing tonote is the address being used. Rather than passing a hostname/port, we passa URI.

ZeroMQ transport types

ZeroMQ supports several different styles of URIs for its transport layer,each of which supports the full gamut of ZeroMQ functionality:

  • tcp://hostname:port sockets let us do "regular" TCP networking
  • inproc://name sockets let us do in-process networking (inter-thread/greenlet) with the same code we'd use for TCP networking
  • ipc:///tmp/filename sockets use UNIX domain sockets for inter-process communication
  • pgm://interface:address:port and epgm://interface:address:port use the OpenPGM library to support multicast over IP (pgm) and over UDP (epgm). Due to the nature of multicast, the pgm and epgm transports can only be used with PUB/SUB socket types (more on this below).

ZeroMQ disconnected operation

One feature that sometimes catches programmers new to ZeroMQ off guard is that itsupports disconnected operation. In the code above, for instance, we couldhave started the server first and the client later. With TCP sockets, thiswouldn't work because the server tries to connect() to the client. In ZeroMQ,the connect() will go through "optimistically," assuming that someone's going tobind to that port later.

What's more is that you can have a client start up, bind to port 8080, perform atransaction with the server, and then shutdown. Another client can then startup, bind to port 8080, and perform another transaction. The server just keepshandling requests, happily "connected" to whatever happens to bind to port 8080.

ZeroMQ message encapsulation

One final aspect of ZeroMQ is that it encapsulates communication intomessages that may be composed of multiple parts. Rather than asking ZeroMQto receive a certain number of bytes from the socket, you ask ZeroMQ to receivea single message. You can also send and receive multipart messages using thezmq.SNDMORE and zmq.RECVMORE options. To send a multipart message, just usezmq.SNDMORE as a second argument to each part's send() except the last:

1. sock.send(part1, zmq.SNDMORE)
2. sock.send(part2, zmq.SNDMORE)
3. sock.send(part3, zmq.SNDMORE)
4. sock.send(final)

 
The client can then ask if there's more to receive:

1. more = True
2. parts = []
3. while more:
4. parts.append(sock.recv())
5. more = sock.getsockopt(zmq.RCVMORE)


ZeroMQ communication patterns

A core concept of ZeroMQ that I've alluded to above but not made explicit is thecommunication patterns supported by ZeroMQ. Because of some of the whiz-bangfeatures such as asynchronous communication and disconnected operation, it'snecessary to apply higher-level patterns than just shoving bytes from oneendpoint to another. ZeroMQ implements this by making you specify a socket_typewhen you call zmq.Context.socket(). Each socket type has a set of "compatible"socket types with which it can communicate, and ZeroMQ will raise an exception ifyou try to communicate between incompatible sockets. Here, I'll describe some ofthe basic patterns:

ZeroMQ request/reply pattern

This pattern is fairly classic; one end (with socket_type=zmq.REQ) sends a request andreceives a response. The other end (with socket_type=zmq.REP) receives arequest and sends a response. A simple echo server might use this pattern. Theserver would be the following:

01. import sys
02. import zmq
03.  
04. context = zmq.Context()
05. sock = context.socket(zmq.REP)
06. sock.bind(sys.argv[1])
07.  
08. while True:
09. message = sock.recv()
10. sock.send('Echoing: ' + message)


Your client then looks like this:

1. import sys
2. import zmq
3. context = zmq.Context()
4.  
5. sock = context.socket(zmq.REQ)
6. sock.connect(sys.argv[1])
7. sock.send(' '.join(sys.argv[2:]))
8. print sock.recv()


Note that in this pattern the zmq.REQ socket must communicate with a seriesof send(), recv() pairs, and the zmq.REP socket must communicate with aseries of recv(), send() pairs. If you try to send or recv two messagesin a row, ZeroMQ will raise an exception. This can cause problems if you have aserver that crashes, for instance, because you'd leave your client in a "danglingsend" state. To recover, you need some other mechanism for timing out requests,closing the socket, and retrying with a new, fresh zmq.REQ socket.

ZeroMQ publish/subscribe pattern

In the publish/subscribe pattern, you have a single socket of type zmq.PUB andzero or more connected zmq.SUB sockets. The zmq.PUB socket broadcastsmessages using send() that the zmq.SUB sockets recv(). Each subscribermust explicitly say what messages it's interested in using the setsockoptmethod. A subscription is a string specifying a prefix of messages thesubscriber is interested in. Thus to subscribe to all messages, the subscriberwould use the call sub_sock.setsockopt(zmq.SUBSCRIBE, ''). Subscribers canalso explicitly unsubscribe from a topic using setsockopt(zmq.UNSUBSCRIBE, ...as well.

One interesting aspect of the zmq.SUB sockets is that they can connect tomultiple endpoints, so that they receive messages from all the publishers. Forexample, suppose you have a server periodically sending messages:

01. import sys
02. import time
03. import zmq
04.  
05. context = zmq.Context()
06. sock = context.socket(zmq.PUB)
07. sock.bind(sys.argv[1])
08.  
09. while True:
10. time.sleep(1)
11. sock.send(sys.argv[1] + ':' + time.ctime())


You could have a client connect to multiple servers with the following code:

01. import sys
02. import zmq
03.  
04. context = zmq.Context()
05. sock = context.socket(zmq.SUB)
06. sock.setsockopt(zmq.SUBSCRIBE, '')
07.  
08. for arg in sys.argv[1:]:
09. sock.connect(arg)
10.  
11. while True:
12. message= sock.recv()
13. print message


To see the multi-subscribe in action, you can start these programs as follows:

1. $ python publisher.py tcp://*:8080 & python publisher.py tcp://*:8081 &
2. $ python subscriber.py tcp://localhost:8080 tcp://localhost:8081


ZeroMQ push/pull pattern

Similar to the pub/sub pattern in the push/pull pattern you have one side (thezmq.PUSH socket) that's doing all the sending, and the other side (zmq.PULL)does all the receiving. The difference between push/pull and pub/sub is that inpush/pull each message is routed to a single zmq.PULL socket, whereas inpub/sub each message is broadcast to all the zmq.SUB sockets. The push/pullpattern is useful for pipelined workloads where a worker process performs someoperations and then sends results along for further processing. It's also usefulfor implementing traditional message queues.

We can see the routing of messages by connecting multiple clients to a singleserver. For this example, we can just change our socket type in the publishercode to be of type zmq.PUSH:

01. import sys
02. import time
03. import zmq
04.  
05. context = zmq.Context()
06. sock = context.socket(zmq.PUSH)
07. sock.bind(sys.argv[1])
08.  
09. while True:
10. time.sleep(1)
11. sock.send(sys.argv[1] + ':' + time.ctime())


Our client is likewise similar to the subscriber code:

01. import sys
02. import zmq
03.  
04. context = zmq.Context()
05. sock = context.socket(zmq.PULL)
06.  
07. for arg in sys.argv[1:]:
08. sock.connect(arg)
09.  
10. while True:
11. message= sock.recv()
12. print message


(Note that we can do the same multi-connect trick we did with the pub/sub, aswell.) Now to see the multi-push, multi-pull, we can start two "pushers" and two"pullers":

1. $ # Start the pushers in one window
2. $ python pusher.py tcp://*:8080 & python pusher.py tcp://*:8081 &
3. $ # Start a puller in another window
4. $ python puller.py tcp://localhost:8080 tcp://localhost:8081
5. $ # Start another puller in a third window
6. $ python puller.py tcp://localhost:8080 tcp://localhost:8081


Conclusion

ZeroMQ provides a handy abstraction for several network communication patternsthat we can use quite easily from Python. If you're thinking of building ahigh-performance distributed system, its certainly worth checking out ZeroMQ asa possible transport layer. Here, I've barely scratched the surface ofwhat's possible with ZeroMQ in Python. In future posts, I'll go a bit deeper,covering topics including:

  • flow control with ZeroMQ
  • advanced communication patterns and devices
  • using ZeroMQ with gevent

I'd love to hear how you're using (or are thinking of using) ZeroMQ for buildingPython applications. In particular, are there any questions you have about ZeroMQthat I might be able to answer in successive posts? Are you using ZeroMQ already,and if so, have you run into any issues? Tell me about it in the comments below!

Published at DZone with permission of its author, Rick Copeland. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Enterprise Integration is a huge problem space for developers, and with so many different technologies to choose from, finding the most elegant solution can be tricky.  FuseSource provides its own gamut of training, services, and tools based on Apache Camel, ActiveMQ, CXF, and ServiceMixJNBridge provides the technology to bridge .NET and Java applications at a low level for maximum performance.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值