ZeroMQ是一种基于消息队列的多线程网络库,提供跨越多种传输协议(TCP:传输控制协议,当传输出现错误时能自动予以纠正;UDP:用户数据包协议,当传输出现错误时会将错误信息丢弃;)的套接字。ZeroMQ是一个可伸缩层,可并行运行,分散在分布式系统间。
1.Request-Reply模式
问答一一对应
问答模式,由请求端发起请求,然后等待回应端应答。一个请求必须对应一个回应,从请求端的角度来看是发-收配对,从回应端的角度是收-发对。请求端可以是1~N个。该模型主要用于远程调用及任务分配等。
使用REQ-REP套接字发送和接受消息是需要遵循一定规律的。客户端首先使用zmq_send()发送消息,再用zmq_recv()接收,如此循环。如果打乱了这个顺序(如连续发送两次)则会报错。类似地,服务端必须先进行接收,后进行发送。
也就是说Request-Reply模式是严格同步的,Request端必须先发送后接受,reply端必须先接受后发送。
req问
package zeromqDemo;
import org.zeromq.ZMQ;
//模拟问答模式中的问
public class ZmqReq {
public static void main(String[] args) {
//创建zeromq客户端
ZMQ.Context zmq = ZMQ.context(1);
//创建一个问的客户端
ZMQ.Socket req = zmq.socket(ZMQ.REQ);
//连接ip和端口
req.connect("tcp://localhost:5555");
//定义标志位
int i = 0;
//接收数据
while (!Thread.currentThread().isInterrupted()){
//发送请求
String str = "hello world";
//发送
req.send(str+i);
System.out.println(str+i);
//接收数据
byte[] recv = req.recv();
//打印
System.out.println(new String(recv));
i++;
}
//关闭资源
req.close();
zmq.close();
}
}
rep答
package zeromqDemo;
import org.zeromq.ZMQ;
//模拟问答模式中的答
public class ZmqRep {
public static void main(String[] args) throws InterruptedException {
//创建zmq实例
ZMQ.Context zmq = ZMQ.context(1);
//创建答的实例
ZMQ.Socket rep = zmq.socket(ZMQ.REP);
//绑定ip和端口
rep.bind("tcp://localhost:5555");
//进行交互
while (true){
//接收数据
Thread.sleep(500);
//发送
rep.send("wo bu hao");
rep.recv();
}
}
}
2.Pub-Sub模式
pub服务端(只发)——sub客户端(只接)
;
先启动订阅,再发布
;
订阅者可以连接多个发布者,轮流接收消息
;
发布者可以连接多个接受者,同时接收相同数据
;
如果发布者没有订阅者与之相连,那它发送的消息将直接被丢弃
;
如果你使用TCP协议,那当订阅者处理速度过慢时,消息会在发布者处堆积
。
在目前版本的ZMQ中,消息的过滤是在订阅者处进行的。也就是说,发布者会向订阅者发送所有的消息,订阅者会将未订阅的消息丢弃
‘使用sleep延缓发布,避免因握手导致连接正在建立,而数据已发出而丢弃’。
发布订阅模式 发布端单向分发数据,且不关心是否把全部信息发送给订阅端。如果发布端开始发布信息时,订阅端尚未连接上来,则这些信息会被直接丢弃。订阅端未连接导致信息丢失的问题,可以通过与请求回应模型组合来解决。订阅端只负责接收,而不能反馈,且在订阅端消费速度慢于发布端的情况下,会在订阅端堆积数据。该模型主要用于数据分发。天气预报、微博明星粉丝可以应用这种经典模型。
在使用SUB套接字时,必须使用subscribe()方法来设置订阅的内容。如果你不设置订阅内容,那将什么消息都收不到。订阅信息可以是任何字符串,可以设置多次。只要消息满足其中一条订阅信息,SUB套接字就会收到。
关于PUB-SUB套接字,还有一点需要注意:你无法得知SUB是何时开始接收消息的。就算你先打开了SUB套接字,后打开PUB发送消息,这时SUB还是会丢失一些消息的,因为建立连接是需要一些时间的。很少,但并不是零。
我们知道在建立TCP连接时需要进行三次握手,会耗费几毫秒的时间,而当节点数增加时这个数字也会上升。在这么短的时间里,ZMQ就可以发送很多很多消息了。举例来说,如果建立连接需要耗时5毫秒,而ZMQ只需要1毫秒就可以发送完这1000条消息。
所以需要发布者和订阅者同步,只有当订阅者准备好时发布者才会开始发送消息。有一种简单的方法来同步PUB和SUB,就是让PUB延迟一段时间再发送消息。现实编程中我不建议使用这种方式,因为它太脆弱了,而且不好控制。不过这里我们先暂且使用sleep的方式来解决。
另一种同步的方式则是认为发布者的消息流是无穷无尽的,因此丢失了前面一部分信息也没有关系.
sub订阅
package zeromqDemo;
import org.zeromq.ZMQ;
//订阅端
public class ZmqSub {
public static void main(String[] args) {
//创建zmq实例
ZMQ.Context zmq = ZMQ.context(1);
//实例化sub
ZMQ.Socket sub = zmq.socket(ZMQ.SUB);
//连接ip和端口
sub.connect("tcp://localhost:5556");
//调用subscribe进行订阅数据
sub.subscribe("".getBytes());
//接收数据
while (!Thread.currentThread().isInterrupted()){
//接收数据
byte[] recv = sub.recv();
System.out.println(new String(recv));
}
//关闭资源
sub.close();
zmq.close();
}
}
pub发布
package zeromqDemo;
import org.zeromq.ZMQ;
//发布端
public class ZmqPub {
public static void main(String[] args) throws InterruptedException {
//zmq
ZMQ.Context zmq = ZMQ.context(1);
//实例化pub
ZMQ.Socket pub = zmq.socket(ZMQ.PUB);
//绑定ip和端口
pub.bind("tcp://localhost:5555");
//发布消息
int i = 0;
while (true){
Thread.sleep(500);
String str = "hello world";
pub.send(str+i);
i++;
}
}
}
3.Push-Poll模式
Server端作为Push端,而Client端作为Pull端
多pull——一个push,轮询平均分配
一个pull——多个push,数据存放队列中,轮询平均消费
如果没有消费,数据会放在队列里,不会销毁
Server端作为Push端,而Client端作为Pull端,如果有多个Client端同时连接到Server端,则Server端会在内部做一个负载均衡,采用平均分配的算法,将所有消息均衡发布到Client端上。如果有多个Server端同时连接到Client端,这里Push与Pull之间的对应关系是多个Push角色对应一个Pull角色,在ZeroMQ中,给这种结构取的名叫做公平队列,这里也就是说将Pull角色理解为一个队列,各个Push角色不断的向这个队列中发送数据。与发布订阅模型相比,推拉模型在没有消费者的情况下,发布的消息不会被消耗掉;在消费者能力不够的情况下,能够提供多消费者并行消费解决方案。该模型主要用于多任务并行处理。
pull拉
package zeromqDemo;
import org.zeromq.ZMQ;
//推拉模式客户端
public class ZmqPull {
public static void main(String[] args) {
//zmq
ZMQ.Context zmq = ZMQ.context(1);
//pull
ZMQ.Socket pull = zmq.socket(ZMQ.PULL);
//连接ip和端口
//如果pull多个,就connect多个tcp
pull.connect("tcp://localhost:5555");
//接收数据
while (!Thread.currentThread().isInterrupted()){
//接收数据
byte[] recv = pull.recv();
System.out.println(new String(recv));
}
//释放资源
pull.close();
zmq.close();
}
}
push推
package zeromqDemo;
import org.zeromq.ZMQ;
//推拉模式服务端
public class ZmqPush {
public static void main(String[] args) throws InterruptedException {
//实例化zmq
ZMQ.Context zmq = ZMQ.context(1);
//push
ZMQ.Socket push = zmq.socket(ZMQ.PUSH);
//绑定ip和端口
push.bind("tcp://localhost:5555");
//发布消息
int i = 0;
while (true){
String str = "hello world";
push.send(str+i);
i++;
Thread.sleep(500);
}
}
}
2.代理服务
发布-订阅代理
我们经常会需要将发布-订阅模式扩充到不同类型的网络中。比如说,有一组订阅者是在外网上的,我们想用广播的方式发布消息给内网的订阅者,而用TCP协议发送给外网订阅者。
我们要做的就是写一个简单的代理服务装置,在发布者和外网订阅者之间搭起桥梁。这个装置有两个端点,一端连接内网上的发布者,另一端连接到外网上。它会从发布者处接收订阅的消息,并转发给外网上的订阅者们。
我们称这个装置为代理,因为它既是订阅者,又是发布者。这就意味着,添加该装置时不需要更改其他程序的代码,只需让外网订阅者知道新的网络地址即可。
外网订阅端
package zmq;
import org.zeromq.ZMQ;
//订阅端
public class Sub {
public static void main(String[] args) {
//创建zmq实例
ZMQ.Context zmq = ZMQ.context(1);
//实例化sub
ZMQ.Socket sub = zmq.socket(ZMQ.SUB);
//连接ip和端口
sub.connect("tcp://localhost:1011");
//调用subscribe进行订阅数据
sub.subscribe("".getBytes());
//接收数据
while (!Thread.currentThread().isInterrupted()){
//接收数据
byte[] recv = sub.recv();
System.out.println(new String(recv));
}
//关闭资源
sub.close();
zmq.close();
}
}
代理
package zmq;
import org.zeromq.ZMQ;
public class ZmqPro {
public static void main(String[] args) throws InterruptedException {
ZMQ.Context context = ZMQ.context(1);
ZMQ.Socket sub = context.socket(ZMQ.SUB);
ZMQ.Socket pub = context.socket(ZMQ.PUB);
sub.connect("tcp://localhost:1012");
sub.subscribe("".getBytes());
pub.bind("tcp://localhost:1011");
//调用代理服务
//null代表是否在本地备份
ZMQ.proxy(sub,pub,null);
pub.close();
sub.close();
context.close();
}
}
内网发布端
package zmq;
import org.zeromq.ZMQ;
//发布端
public class Pub {
public static void main(String[] args) throws InterruptedException {
//zmq
ZMQ.Context zmq = ZMQ.context(1);
//实例化pub
ZMQ.Socket pub = zmq.socket(ZMQ.PUB);
//绑定ip和端口
pub.bind("tcp://localhost:1012");
//发布消息
int i = 0;
while (true){
Thread.sleep(500);
String str = "hello world";
pub.send(str+i);
i++;
}
}
}