官网地址:https://zeromq.org/get-started/
java-API文档:https://www.javadoc.io/doc/org.zeromq/jeromq/latest/index.html
ZreoMQ支持许多语言,这里用的都是java的。
本文图片是复制粘贴的,这个也没什么好画的,主要就是分享一下消息模式的代码。
原文链接:ZMQ简介
官方github:代码
一、简介
高性能的异步消息传递库,旨在用于分布式或者并发应用程序。它提供了一个消息队列,但与面向消息的中间件不同,ZeroMQ 系统可以在没有专用消息代理的情况下运行。
ZeroMQ 支持各种传输(TCP、进程内、进程间、多播<一个数据发送给不同子网中的一组接收者>、WebSocket 等)上的常见消息传递模式(发布/订阅、请求/回复、客户端/服务器等),使进程间消息传递变得简单作为线程间消息传递。这使得代码清晰、模块化且非常易于扩展。
ZeroMQ的0的意思:
0表示无代理、零延迟、零成本和零管理。
通俗的来说,零是指渗透到项目中的极简主义文化。我们通过消除复杂性1而不是暴露新功能来增加功能。
二、引入maven项目
<dependency>
<groupId>org.zeromq</groupId>
<artifactId>jeromq</artifactId>
<version>0.5.2</version>
</dependency>
<!-- for the latest SNAPSHOT --><!--最新的,这俩一个就够了-->
<dependency>
<groupId>org.zeromq</groupId>
<artifactId>jeromq</artifactId>
<version>0.5.3-SNAPSHOT</version>
</dependency>
<!-- If you can't find the latest snapshot -->
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
三、消息模式
1. 应答模式
REQ-REP
:客户端首先要使用send()
发送消息,再使用recv()
接收,如果打乱了顺序,就会报错。服务端也是这样,必须先进行接收,后进行发送。
- 客户端
public class Client {
public static void main(String[] args) {
ZContext context = new ZContext(1);
ZMQ.Socket socket = context.createSocket(SocketType.REQ);
socket.connect("tcp://localhost:5555");
while (true) {
socket.send("1");
byte[] recv = socket.recv();
System.out.println(new String(recv));
}
// 这是需要关流的
context.close();
socket.close();
}
}
- 服务端
public class Server {
public static void main(String[] args) throws InterruptedException {
ZContext context = new ZContext(1);
ZMQ.Socket socket = context.createSocket(SocketType.REP);
socket.bind("tcp://*:5555");
while (true){
byte[] recv = socket.recv();
System.out.println(new String(recv));
socket.send("接收到了,OKK");
}
}
}
2. 订阅发布模式
PUB-SUB
:这个组合是异步的,客户单在一个循环体中使用recv()
接收消息,使用SUB套接字时,不可以发送消息。服务端也是,只可以send()
发送消息,不可以使用PUB套接字接收消息。
注意:因为无法得知SUB是何时开始接收消息的,就算先打开了SUB,后打开PUB发送消息,这时候的SUB还是会丢失一些消息,因为TCP建立链接是需要时间的。可以先让PUB休眠一段时间,确定链接到了再发送消息。
- 客户端
public class ClientA {
public static void main(String[] args) {
ZContext context = new ZContext(3);
ZMQ.Socket socket = context.createSocket(SocketType.SUB);
socket.connect("tcp://localhost:5555");
// 设置,指订阅前缀为A 的消息,设置为“” 表示全部接受
socket.subscribe("A".getBytes());
while (!Thread.currentThread().isInterrupted()) {
byte[] recv = socket.recv();
System.out.println(new String(recv));
}
}
}
public class ClientB {
public static void main(String[] args) throws InterruptedException {
ZContext context = new ZContext(3);
ZMQ.Socket socket = context.createSocket(SocketType.SUB);
socket.connect("tcp://localhost:5555");
socket.subscribe("B".getBytes());
while (!Thread.currentThread().isInterrupted()) {
byte[] recv = socket.recv();
System.out.println(new String(recv));
}
}
}
- 服务端
public class Server {
public static void main(String[] args) throws InterruptedException {
ZContext context = new ZContext(1);
ZMQ.Socket socket = context.createSocket(SocketType.PUB);
socket.bind("tcp://*:5555");
// 设置一定的休眠时间,确保能链接上。
Thread.sleep(1000);
Scanner sc = new Scanner(System.in);
while (true) {
String next = sc.next();
socket.send(next.getBytes(StandardCharsets.UTF_8), 0);
}
}
}
3. 管道模式
PUSH-PULL:将数据分发到下游链接的所有节点,数据是均衡分布的(循环发布)。
在这里插入图片描述
- 发送器
//定义最上面的分发任务的
public class Master {
public static void main(String[] args) throws InterruptedException {
ZContext context = new ZContext(1);
ZMQ.Socket socket = context.createSocket(SocketType.PUSH);
// 工人会链接到这个用来接受消息的
socket.bind("tcp://*:5555");
// 只执行一次。告诉接收器开始工作
ZMQ.Socket slink = context.createSocket(SocketType.PUSH);
slink.connect("tcp://localhost:6666");
// 设置休眠,保证连接
Thread.sleep(1000);
//生成100个任务,分发给工人
slink.send("开始工作");
for (int i = 1; i < 101; i++) {
// 设置线程休眠,观察动态加入工作
Thread.sleep(1000)
socket.send(String.valueOf(i));
}
}
}
- 工作者
//定义 工作者,(可以多写几个 )除了执行任务逻辑不同,其他的相同
public class Worker1 {
public static void main(String[] args) throws InterruptedException {
ZContext context = new ZContext(1);
//链接发送器
ZMQ.Socket socket = context.createSocket(SocketType.PULL);
socket.connect("tcp://localhost:5555");
//链接接收器
ZMQ.Socket slink = context.createSocket(SocketType.PUSH);
slink.connect("tcp://localhost:6666");
//执行任务
while (true){
//接受数据
byte[] recv = socket.recv();
String s = new String(recv);
System.out.println(s);
//发送数据
// Thread.sleep(1000);
Thread.sleep(100);
slink.send("A发送的消息:"+s);
}
}
}
- 接收器
//定义接收器
public class Receiver {
public static void main(String[] args) {
ZContext context = new ZContext(1);
ZMQ.Socket socket = context.createSocket(SocketType.PULL);
socket.bind("tcp://*:6666");
// 只负责接收数据
while (true){
System.out.println(new String(socket.recv()));
}
}
}
我遇到的一些问题:
上面描述的,必须得在确保链接上的情况发送消息,不然可能会出现,发送的消息,全部涌入一个节点,或者消息发送出去没有接收到。(这个ZMQ好快的)
再就是:
- 如果你停止接收器,消息是不会丢失的 ,上面的工作者会阻塞,当你重新启动接收器,会发现消息还是会整常的接收到。
- 当程序运行时,如果其中一个工作者宕机了,消息会就会均衡分配到其他的工作者;当宕机的工作者重新开机,在它链接到发送者之后,还是可以正常的收发消息,这就是上面描述的动态添加节点。
官网还有两个模式,我好像也用不到,之后用到了再继续给大家分享。