ZeroMQ初探
概述
ZeroMQ(也称为 ØMQ,0MQ 或 zmq)是一个可嵌入的网络通讯库(对 Socket 进行了封装)。 它提供了携带跨越多种传输协议(如:进程内,进程间,TCP 和多播)的原子消息的 sockets 。 有了ZeroMQ,我们可以通过发布-订阅、任务分发、和请求-回复等模式来建立 N-N 的 socket 连接。 ZeroMQ 的异步 I / O 模型为我们提供可扩展的基于异步消息处理任务的多核应用程序。 它有一系列语言API(几乎囊括所有编程语言),并能够在大多数操作系统上运行。
传统的 TCP Socket 的连接是1对1的,可以认为“1个 socket = 1个连接”,每一个线程独立维护一个 socket 。但是 ZMQ 摒弃了这种1对1的模式,ZMQ的 Socket 可以很轻松地实现1对N和N对N的连接模式,一个 ZMQ 的 socket 可以自动地维护一组连接,用户无法操作这些连接,用户只能操作套接字而不是连接本身。所以说在 ZMQ 的世界里,连接是私有的。
三种基本模型
ZMQ 提供了三种基本的通信模型,分别是 Request-Reply 、Publish-Subscribe 和 Parallel Pipeline ,接下来举例说明三种模型并给出相应的代码实现。
Request-Reply(请求-回复)
以 “Hello World” 为例。客户端发起请求,并等待服务端回应请求。客户端发送一个简单的 “Hello”,服务端则回应一个 “World”。可以有 N 个客户端,一个服务端,因此是 1-N 连接。
服务端代码如下:
- hwserver.java
import org.zeromq.ZMQ;
public class hwserver {
public static void main(String[] args) throws InterruptedException {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket responder = context.socket(ZMQ.REP);
responder.bind("tcp://*:5555");
while (!Thread.currentThread().isInterrupted()) {
byte[] request = responder.recv(0);
System.out.println("Received" + new String(request));
Thread.sleep(1000);
String reply = "World";
responder.send(reply.getBytes(),0);
}
responder.close();
context.term();
}
}
- hwserver.py
import time
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")
while True:
message = socket.recv()
print("Received request: %s" % message)
# Do some 'work'
time.sleep(1)
socket.send(b"World")
客户端代码如下:
- hwclient.java
import org.zeromq.ZMQ;
public class hwclient {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket requester = context.socket(ZMQ.REQ);
requester.connect("tcp://localhost:5555");
for (int requestNbr = 0; requestNbr != 10; requestNbr++) {
String request = "Hello";
System.out.println("Sending Hello" + requestNbr);
requester.send(request.getBytes(),0);
byte[] reply = requester.recv(0);
System.out.println("Reveived " + new String(reply) + " " + requestNbr);
}
requester.close();
context.term();
}
}
- hwclient.py
import zmq
context = zmq.Context()
print("Connecting to hello world server...")
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")
for request in range(10):
print("Sending request %s ..." % request)
socket.send(b"Hello")
message = socket.recv()
print("Received reply %s [ %s ]" % (request,message))
从以上的过程,我们可以了解到使用 ZMQ 写基本的程序的方法,需要注意的是:
- 服务端和客户端无论谁先启动,效果是相同的,这点不同于 Socket。
- 在服务端收到信息以前,程序是阻塞的,会一直等待客户端连接上来。
- 服务端收到信息后,会发送一个 “World” 给客户端。值得注意的是一定是客户端连接上来以后,发消息给服务端,服务端接受消息然后响应客户端,这样一问一答。
- ZMQ 的通信单元是消息,它只知道消息的长度,并不关心消息格式。因此,你可以使用任何你觉得好用的数据格式,如 Xml、Protocol Buffers、Thrift、json 等等。
Publish-Subscribe(发布-订阅)
下面以一个天气预报的例子来介绍该模式。
服务端不断地更新各个城市的天气,客户端可以订阅自己感兴趣(通过一个过滤器)的城市的天气信息。
服务端代码如下:
- wuserver.java
import org.zeromq.ZMQ;
import java.util.Random;
public class wuserver {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket publisher = context.socket(ZMQ.PUB);
publisher.bind("tcp://*:5556");
publisher.bind("icp://weather");
Random srandom = new Random(System.currentTimeMillis());
while (!Thread.currentThread().isInterrupted()) {
int zipcode, temperature, relhumidity;
zipcode = 10000 + srandom.nextInt(10000);
temperature = srandom.nextInt(215) - 80 + 1;
relhumidity = srandom.nextInt(50) + 10 + 1;
String update = String.format("%05d %d %d", zipcode, temperature, relhumidity);
}
publisher.close();
context.term();
}
}
- wuserver.py
from random import randrange
import zmq
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))
客户端代码如下:
- wuclient.java
import org.zeromq.ZMQ;
import java.util.StringTokenizer;
public class wuclient {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket suscriber = context.socket(ZMQ.SUB);
suscriber.connect("tcp://localhost:5556");
String filter = (args.length > 0) ? args[0] : "10001";
suscriber.suscribe(filter.getBytes()); //过滤条件
int update_nbr;
long total_temp = 0;
for (update_nbr = 0; update_nbr < 100; update_nbr++) {
String string = suscriber.recvStr(0).trim();
StringTokenizer sscanf = new StringTokenizer(string, " ");
int zipcode = Integer.valueOf(sscanf.nextToken());
int temperature = Integer.valueOf(sscanf.nextToken());
int relhumidity = Integer.valueOf(sscanf.nextToken());
total_temp += temperature;
}
System.out.println("Average temperature for zipcode '" + filter + "' was " + (int) (total_temp / update_nbr));
suscriber.close();
context.term();
}
}
- wuclient.py
import sys
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
print("Collecting updates from weather server...")
socket.connect("tcp://localhost:5556")
zip_filter = sys.argv[1] if len(sys.argv) > 1 else "10001"
if isinstance(zip_filter, bytes):
zip_filter = zip_filter.decode('ascii')
socket.setsockopt_string(zmq.SUBSCRIBE, zip_filter)
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 %dF" % (zip_filter, total_temp / (update_nbr + 1)))
服务器端生成随机数 zipcode、temperature、relhumidity 分别代表城市代码、温度值和湿度值,然后不断地广播信息。而客户端通过设置过滤参数,接受特定城市代码的信息,最终将收集到的温度求平均值。
需要注意的是:
- 与 “Hello World” 例子不同的是,Socket 的类型变成 ZMQ.PUB 和 ZMQ.SUB 。
- 客户端需要设置一个过滤条件,接收自己感兴趣的消息。
- 发布者一直不断地发布新消息,如果中途有订阅者退出,其他均不受影响。当订阅者再连接上来的时候,收到的就是后来发送的消息了。这样比较晚加入的或者是中途离开的订阅者必然会丢失掉一部分信息。这是该模式的一个问题,即所谓的 "Slow joiner" 。
Parallel Pipeline
Parallel Pipeline 处理模式如下:
- ventilator 分发任务到各个 worker
- 每个 worker 执行分配到的任务
- 最后由 sink 收集从 worker 发来的结果
- taskvent.java
import org.zeromq.ZMQ;
import java.io.IOException;
import java.util.Random;
import java.util.StringTokenizer;
public class taskvent {
public static void main(String[] args) throws IOException {
ZMQ.Context context = new ZMQ.context(1);
ZMQ.Socket sender = context.socket(ZMQ.PUSH);
sender.bind("tcp://*:5557");
ZMQ.Socket sink = context.socket(ZMQ.PUSH);
sink.connect("tcp://localhost:5558");
System.out.println("Please enter when the workers are ready: ");
System.in.read();
System.out.println("Sending task to workes\n");
sink.send("0",0);
Random srandom = new Random(System.currentTimeMillis());
int task_nbr;
int total_msec = 0;
for (task_nbr = 0; task_nbr < 100; task_nbr++) {
int workload;
workload = srandom.nextInt(100) + 1;
total_msec += workload;
System.out.print(workload + ".");
String string = String.format("%d", workload);
sender.send(string, 0);
}
System.out.println("Total expected cost: " + total_msec + " msec");
sink.close();
sender.close();
context.term();
}
}
- taskvent.py
import zmq
import time
import random
try:
raw_input
except NameError:
raw_input = input
context = zmq.Context()
sender = context.socket(zmq.PUSH)
sender.bind("tcp://*:5557")
sink = context.socket(zmq.PUSH)
sink.connect("tcp://localhost:5558")
print("Please enter when workers are ready: ")
_ = raw_input()
print("Sending tasks to workers...")
sink.send(b'0')
random.seed()
total_msec = 0
for task_nbr in range(100):
workload = random.randint(1, 100)
total_msec += workload
sender.send_string(u'%i' % workload)
print("Total expected cost: %s msec" % total_msec)
time.sleep(1)
- taskwork.java
import org.zeromq.ZMQ;
public class taskwork {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket receiver = context.socket(ZMQ.PULL);
receiver.connect("tcp://localhost:5557");
ZMQ.Socket sender = context.socket(ZMQ.PUSH);
sender.connect("tcp://localhost:5558");
while (!Thread.currentThread().isInterrupted()) {
String string = receiver.recv(0).trim();
Long mesc = Long.parseLong(string);
System.out.flush();
System.out.print(string + ".");
Sleep(mesc);
sender.send("".getBytes(), 0);
}
sender.close();
receiver.close();
context.term();
}
}
- taskwork.py
import zmq
import time
import sys
context = zmq.Context()
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://localhost:5557")
sender = context.socket(zmq.PUSH)
sender.connect("tcp://localhost:5558")
while True:
s = receiver.recv()
sys.stdout.write('.')
sys.stdout.flush()
time.sleep(int(s) * 0.001)
sender.send(b'')
- tasksink.java
import org.zeromq.ZMQ;
public class tasksink {
public static void main(String[] args) {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket receiver = context.socket(ZMQ.PULL);
receiver.bind("tcp://*:5558");
String string = new String(receiver.recv(0));
long tstart = System.currentTimeMillis();
int task_nbr;
int total_mesc = 0;
for (task_nbr = 0; task_nbr < 100; task_nbr++) {
string = new String(receiver.recv(0).trim());
if ((task_nbr / 10) * 10 == task_nbr) {
System.out.print(":");
} else {
System.out.print(".");
}
}
long tend = System.currentTimeMillis();
System.out.println("\nTotal elapsed time: " + (tend - tstart) + "msec");
receiver.close();
context.term();
}
}
- tasksink.py
import time
import zmq
import sys
context = zmq.Context()
receiver = context.socket(zmq.PULL)
receiver.bind("tcp://*:5558")
s = receiver.recv()
tstart = time.time()
for task_nbr in range(1, 100):
s = receiver.recv()
if task_nbr % 10 == 0:
sys.stdout.write(':')
else:
sys.stdout.write('.')
sys.stdout.flush()
tend = time.time()
print("Total elapsed time: %d msec" % ((tend - tstart) * 1000))
以下两点需要注意:
- ventilator 使用 ZMQ.PUSH 来分发任务;worker 用 ZMQ.PULL 来接收任务,用 ZMQ.PUSH 来发送结果;sink 用 ZMQ.PULL 来接收 worker 发来的结果。
- ventilator 既是服务端,也是客户端(此时服务端是 sink);worker 在两个过程中都是客户端;sink 也一直都是服务端。
参考资料
欢迎进入博客 :linbingdong.com 获取最新文章哦~
欢迎关注公众号: FullStackPlan 获取更多干货哦~
ZeroMQ初探的更多相关文章
- .NET文件并发与RabbitMQ(初探RabbitMQ)
本文版权归博客园和作者吴双本人共同所有.欢迎转载,转载和爬虫请注明原文地址:http://www.cnblogs.com/tdws/p/5860668.html 想必MQ这两个字母对于各位前辈们和老司 ...
- 消息队列性能对比——ActiveMQ、RabbitMQ与ZeroMQ(译文)
Dissecting Message Queues 概述: 我花了一些时间解剖各种库执行分布式消息.在这个分析中,我看了几个不同的方面,包括API特性,易于部署和维护,以及性能质量..消息队列已经被分 ...
- 初探领域驱动设计(2)Repository在DDD中的应用
概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...
- CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探
CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...
- ZeroMQ:云时代极速消息通信库
ZeroMQ:云时代极速消息通信库(大规模|可扩展|低成本|高效率解决之道,大规模分布式|多线程应用程序|消息传递架构构建利器) [美]Pieter Hintjens(皮特.亨特金斯)著 卢涛 李 ...
- 从273二手车的M站点初探js模块化编程
前言 这几天在看273M站点时被他们的页面交互方式所吸引,他们的首页是采用三次加载+分页的方式.也就说分为大分页和小分页两种交互.大分页就是通过分页按钮来操作,小分页是通过下拉(向下滑动)时异步加载数 ...
- JavaScript学习(一) —— 环境搭建与JavaScript初探
1.开发环境搭建 本系列教程的开发工具,我们采用HBuilder. 可以去网上下载最新的版本,然后解压一下就能直接用了.学习JavaScript,环境搭建是非常简单的,或者说,只要你有一个浏览器,一个 ...
- 以ZeroMQ谈消息中间件的设计【译文】
本文主要是探究学习比较流行的一款消息层是如何设计与实现的 ØMQ是一种消息传递系统,或者乐意的话可以称它为"面向消息的中间件".它在金融服务,游戏开发,嵌入式系统,学术研究和航空航 ...
- React Native初探
前言 很久之前就想研究React Native了,但是一直没有落地的机会,我一直认为一个技术要有落地的场景才有研究的意义,刚好最近迎来了新的APP,在可控的范围内,我们可以在上面做任何想做的事情. P ...
随机推荐
- Codeforces#348DIV2/VK CUP 2016
昨天第一次开大小号打cf,发现原来小号提交之后大号在此提交同样的代码会被skipped掉,然后之后提交的代码都不记分,昨天a,b,c都是水题 A 题意:问一个物品最多能被分成多少份,分成的连续两份不能 ...
- 在阿里云ECS(CentOS6.5)上安装tomcat
切换到你要安装的目录下 命令: cd /home/ 下载你要安装的tomcat 命令: wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7. ...
- Eclipse中GIT插件更新工程到之前版本
因为之前好多次因为对项目文件删除后,发现删除的文件里有些功能模块还是需要的,所以需要恢复到之前的版本.但是一直不知道怎么操作才能恢复到之前版本,索性就直接把工程删了,重新导入,但是这太暴力了,所以看了 ...
- Laravel 安装
其实,laravel的安装网上给了很多方法,但是你可以直接根据laravel中国官网http://www.golaravel.com/docs/4.1/installation/给出的三种方法,选择其 ...
- ERP软件数据库覆盖数据恢复成功/重装数据库系统软件,导致同名文件覆盖
ERP软件数据库覆盖数据恢复成功/重装数据库系统软件,导致同名文件覆盖 [数据恢复故障描述] 上海某酒店ERP软件原来安装在C盘上,用户误操作把软件进行了卸载,发现软件没有了, 但操作之前没有把原 ...
- iOS开发UITableView基本使用方法总结
本文为大家呈现了iOS开发中UITableView基本使用方法总结.首先,Controller需要实现两个delegate ,分别是UITableViewDelegate 和UITableViewDa ...
- 【转】HTTP响应报文与工作原理详解
超文本传输协议(Hypertext Transfer Protocol,简称HTTP)是应用层协议.HTTP 是一种请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求;服务器接到 ...
- Mybatis中常见的SQL DML
1.sql select 查询 <select id="query" resultType="CfCurrbusilogEntity" > sele ...
- iOS 之 后台下载,前台显示模式,双 block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //耗时的操作 NSURL *url ...
- js模块化开发——前端模块化
在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可:如今CPU.浏览器性能得到了极大的提升,很多页面逻辑迁移到了客 户端(表单验证等),随着web2.0时代的到来,Ajax技术 ...