ZeroMQ
关于ZeroMQ的介绍和一些描述在本文中就不写了,详细可以见文章末尾的参考文章,主要写在开发过程中最长碰见的两种模式请求响应模式和发布订阅模式的例子,同样也是自己去复习一哈。
maven
<dependency>
<groupId>org.zeromq</groupId>
<artifactId>jeromq</artifactId>
<version>0.5.3-SNAPSHOT</version>
</dependency>
请求—响应(REQ—REP)模式:
对于请求响应模式来说一个请求必须对应一个响应,也就是说一个send()必须对应一个recv(),消息不会丢失。
如果再客户端只send没有recv那么服务端就会阻塞,并且接收不到客户端发来的消息。
如果再服务端只recv没有send那么客户端就会阻塞。
1.一个请求对应一个响应的正常代码
客户端代码:
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
/**
* @Author halon
* @create 2021/6/21 16:28
*/
public class ZeroMQClient {
private static ZContext context = new ZContext(1);
public static void main(String[] args) {
ZMQ.Socket socket = context.createSocket(SocketType.REQ);
socket.connect("tcp://localhost:6666");
socket.send("hello");
byte[] recv = socket.recv();
System.out.println(new String(recv));
}
}
服务端代码:
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
import java.util.concurrent.TimeUnit;
/**
* @Author halon
* @create 2021/6/21 15:29
*/
public class ZeroMQServer {
private static ZContext context = new ZContext(1);
public static void main(String[] args) throws InterruptedException {
ZMQ.Socket socket = context.createSocket(SocketType.REP);
socket.bind("tcp://localhost:6666");
byte[] recv = socket.recv();
System.out.println(new String(recv));
if ("hello".equals(new String(recv))) {
socket.send("success");
} else {
socket.send("error");
}
}
}
控制台输出:
ZeroMQClient控制台:success
ZeroMQServer控制台:hello
2.连续两次send且并中间没有进行recv的时候(抛出异常):
修改客户端,服务端代码保持不变
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
/**
* @Author halon
* @create 2021/6/21 16:28
*/
public class ZeroMQClient {
private static ZContext context = new ZContext(1);
public static void main(String[] args) {
ZMQ.Socket socket = context.createSocket(SocketType.REQ);
socket.connect("tcp://localhost:6666");
//增加一次发送
socket.send("more");
socket.send("hello");
byte[] recv = socket.recv();
System.out.println(new String(recv));
}
}
客户端抛出异常并且服务端阻塞:
Errno 156384763 : Operation cannot be accomplished in current state
注意:即使客户端连续两次send,服务端连续两次recv客户端同样也会抛出异常。但是如果客户端正常send一次,而服务端连续两次recv会导致客户端和服务端都陷入阻塞。
3.如果客户端需要连续发送两条消息。
客户端代码:
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
/**
* @Author halon
* @create 2021/6/21 16:28
*/
public class ZeroMQClient {
private static ZContext context = new ZContext(1);
public static void main(String[] args) {
ZMQ.Socket socket = context.createSocket(SocketType.REQ);
socket.connect("tcp://localhost:6666");
//增加一次发送
socket.sendMore("more");
socket.send("hello");
byte[] recv = socket.recv();
System.out.println(new String(recv));
}
}
服务端代码:
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
import java.util.concurrent.TimeUnit;
/**
* @Author halon
* @create 2021/6/21 15:29
*/
public class ZeroMQServer {
private static ZContext context = new ZContext(1);
public static void main(String[] args) throws InterruptedException {
ZMQ.Socket socket = context.createSocket(SocketType.REP);
socket.bind("tcp://localhost:6666");
byte[] recv = socket.recv();
System.out.println(new String(recv));
//多次接收
recv = socket.recv();
System.out.println(new String(recv));
if ("hello".equals(new String(recv))) {
socket.send("success");
} else {
socket.send("error");
}
}
}
输出结果:
ZeroMQClient:success
ZeroMQServer:more
:hello
如果客户端是用了sendMore+send发送连续的两条消息,而在服务端只recv了一次,服务端会抛出异常。如果把服务端的代码换成一个死循环一样会抛出该异常。循环代码如下:
while (true) {
byte[] recv = socket.recv();
System.out.println(new String(recv));
// recv = socket.recv();
// System.out.println(new String(recv));
if ("hello".equals(new String(recv))) {
socket.send("success");
} else {
socket.send("error");
}
}
异常信息:
#Errno 156384763 : Operation cannot be accomplished in current state
对于客户端sendMore n次+send一次,服务端recv n+1次 同样是不会报错的。
sendMore不用对应一个recv,如果对应就会抛出异常。
发布—订阅(PUB—SUB)模式:
客户端(订阅端)代码:
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
/**
* @Author halon
* @create 2021/6/21 16:28
*/
public class ZeroMQClient {
private static ZContext context = new ZContext(1);
public static void main(String[] args) {
ZMQ.Socket socket = context.createSocket(SocketType.SUB);
socket.connect("tcp://localhost:6666");
//设置订阅的主题
socket.subscribe("halon");
while (true) {
byte[] recv = socket.recv();
if (recv != null) {
System.out.println(new String(recv));
}
}
}
}
服务端(发布端)代码:
package com.zmq;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
import org.zeromq.ZMQ;
import java.util.concurrent.TimeUnit;
/**
* @Author halon
* @create 2021/6/21 15:29
*/
public class ZeroMQServer {
private static ZContext context = new ZContext(1);
public static void main(String[] args) throws InterruptedException {
ZMQ.Socket socket = context.createSocket(SocketType.PUB);
socket.bind("tcp://localhost:6666");
int i = 0;
while (true) {
//":"左边是主题 右边是消息的内容
boolean hello = socket.send("halon:hello" + i++);
System.out.println(hello);
TimeUnit.SECONDS.sleep(3);
}
}
}
控制台输出结果:
ZeroMQClient: halon:hello0
halon:hello1
...
ZeroMQServer: halon:hello0
halon:hello1
...
注意:该发布订阅模式使用的是被动接收模式,也就是说消息是直接推送给了客户端,如果客户端中间宕机再重启有可能造成消息的丢失。
拓展:Kafka也是基于发布订阅模式的MQ,但是Kafka使用的是订阅方(消费者)主动拉取的模式。
关于发布订阅模式订阅者被动接收和主动拉取的比较:首先不管是前者还是后者因为都是发布订阅模式所以都是广播。
被动接收:速率取决于服务端推送的速度,如果在消费者集群里,消费者之间的消费速度差异很大,那么就会导致消费者的资源浪费问题。
主动拉取:在消息足够多的情况下,消费速率取决于消费者,如果消费者消费了所有的信息,那么消费者需要不断去查看是否有新的消息需要去拉取,也会消耗一部分的性能。
参考与扩展资料:
ZeroMQ官方
ZeroMQ无锁队列分析
消息队列ZeroMQ实践