消息中间件学习九--ZeroMQ
1.了解ZeroMQ
概述:
1.ZeroMQ号称是“史上最快的消息队列”,基于c语言开发的,
实时流处理sorm的task之间的通信就是用的zeroMQ。
2.引用官方说法,“ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的传输层,
像框架一样的一个socket library,他使得Socket编程更加简单、简洁和性能更高。
3.是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。
4.ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。
现在还未看到它们的成功。但是,它无疑是极具前景的、
并且是人们更加需要的“传统”BSD套接字之上的一层封装。
5.ZMQ让编写高性能网络应用程序极为简单和有趣。
” 确实,它跟RabbitMQ,ActiveMQ之类有着相当本质的区别,ZeroMQ根本就不是一个消息队列服务器,
更像是一组底层网络通讯库,对原有的Socket API加上一层封装,是我们操作更简便。
使用时只需要引入相应的jar包即可。
模式:
ZeroMQ与其他MQ类似,也实现了3中最基本的工作模式:发布-订阅,请求-应答,管道.
ZeroMQ特点:
1).嵌入式消息组件
与rabbitMQ,ActiveMQ有很大的不同,如果说rabbitMQ已经近乎是一个小型操作系统,
那么ZeroMQ就像是一个嵌入在操作系统内的一个组件,说白了ZeroMQ就是一组jar包,
直接嵌入到项目中就可以运行,它不需要一台独立的服务器来承载整个消息系统。
ZeroMQ关注的不是消息的可靠送达,而是着眼于端到端的发送,
接收…它希望的是尽快完成任务,而不介意部分消息的丢失。
但这也并不是说他完全没有持久化的功能,
ZeroMQ是具有一定的本地持久化的功能的,
但是能保存的数据量比较有限,而且是暂存于内存中的。
2).高的离谱的吞吐量
显示的是每秒钟发送和接受的消息数。整个过程共产生1百万条1K的消息,测试环境为Windows Vista。
从测试数据可以看出,ZeroMQ的性能远远高于其它3个MQ。
或者说ZeroMQ与其他3各MQ根本就不再一个量级上比较合适。
至于这样的原因跟ZeroMQ的定位,以及对消息的处理方式有很大关联。
ZeroMQ对于消息的处理可以说除却请求-应答模式之外,基本就是不关系消息是否丢失,它只管发送。
ZeroMQ的定位,它的创始人一直在其社区表示,团队将立志于把ZeroMQ融入到Linux内核中去。
基于以上两点,高效的处理速度就成了它必不可少的特点之一。
3.多核下的线程绑定
传统的多线程并发模式一般会采用锁,临界区,信号量等技术来控制,
而ZeroMQ给出的建议是:在创建IO时不要超出CPU核数。
当我们创建一个上下文时都会有这么一句代码“Context context = ZMQ.context(1);”
这里就指定了IO线程数。通常来说一个线程足矣。
但是如果希望创建多个IO线程,最好不要超出CPU核数,
因为此时ZeroMQ会将工作线程其实就是那个Poller绑定到每一个核,
免除了线程上下文切换带来的开销。
4.与其它MQ相比
关于ZeroMQ与其他几个MQ之间的比较,我们在TPS,并发性,持久化,技术点以及扩展性这几个方面进行展开。
一.TPS
显示的是每秒钟发送和接受的消息数。整个过程共产生1百万条1K的消息,
测试环境为Windows Vista。
明显的ZeroMQ最好,其余三者差不多。
二.持久化消息比较
ZeroMq原生是不支持的,仅支持相当有限的本地缓存,
如需要消息持久化需要自己进行扩展。
ActiveMq和rabbitMq都支持。
三.并发性
虽然ZeroMQ在高并发环境下不会出问题,但是有可能会导致本地的缓存区被塞满而导致消息丢失的情况。
所以不推荐在并发量较高的情境下使用ZeroMQ.
查了下资料发现,ActiveMQ在发送到queue的消息并发较多时,消费端只能接收一部分,
比如100条消息在较短的时间内发入,总有10来条接收不到,存放在服务器上,
而且这些消息一直不能主动发送出来,后面继续进入的消息都能正常处理,
最终只有重新启动服务消费端才能接收到那部分剩下的消息。
而RabbitMQ,从实现语言来看,它是并发性最好的,
原因是它的实现语言是天生具备高并发高可用的erlang语言。
四.技术点以及扩展性
ok,首先就扩展性而言,那毫无疑问的是ZeroMQ最强,
其余其中MQ都已经是成形的产品,已经是一款应用程序了。
而ZeroMQ说白了就是一组库函数。基于这种情况,
我们可以按自己的需要实现IPollEvent以及ZObject来开发适合自己的Socket组件,
至于它对于消息持久化的不支持,只是原生不支持,因为它的定位不是吃保证可靠的消息传输。
所以在可靠性这部分我们完全可以按自己的需求进行扩展。
一组lib的扩展度明显是宽于产品级的rabbitMQ之类的产品。
技术上虽然ZeroMQ立志于成为Linux内核的消息组件,
但是不得不说它的开源社区活跃度是远远不及RabbitMQ或者ActiveMQ。
或许是处于它的可靠性考虑,它的应用场景比较受限制。
可靠性上虽然ActiveMQ也具备,只是性能上相比于RabbitMQ还是有一定差距,
所以大部分的MQ选型都是RabbitMQ。
2.搭建(SpringBoot集成ZeroMQ)
第一步:导入依赖(pom.xml)
<dependency> <groupId>org.zeromq</groupId> <artifactId>jeromq</artifactId> <version>0.3.1</version> </dependency>
2.第二步:创建发送工具类
/** * ZMQ发布数据 */ public class ZmqPubClient { /** * ZMQ启动线程数 */ private static int ZMQThreadCount = Integer.parseInt("1"); /** * ZMQ数据广播端口 */ private static int ZMQSendPort = Integer.parseInt("7111"); private static ZMQ.Context context = null; private static ZMQ.Socket pubSock = null; /** * 初始化ZMQ对象 */ private static void initZMQ() { if (context == null) { context = ZMQ.context(ZMQThreadCount); } if (ZMQSendPort != 0) { pubSock = context.socket(ZMQ.PUB); String bindUri = "tcp://*:" + ZMQSendPort; pubSock.bind(bindUri); } else { throw new RuntimeException("Error!"); } } private static ZMQ.Socket getPubSock() { if (pubSock == null) { initZMQ(); } return pubSock; } private static ZMQ.Socket getPubSock(int port) { if (context == null) { context = ZMQ.context(ZMQThreadCount); } ZMQ.Socket socket = context.socket(ZMQ.PUB); String binUri = "tcp://*" + port; socket.bind(binUri); return socket; } public static void sendData(String msg) { getPubSock().send(msg, ZMQ.NOBLOCK); } public static void sendData(byte[] msg) { getPubSock().send(msg); } public static void sendData(int port, String msg) { ZMQ.Socket socket = getPubSock(port); socket.send(msg); socket.close(); } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 100; i++) { sendData("测试测试".getBytes()); System.out.println("发送测试"); Thread.sleep(1000); } } }
第三步:创建接收类
/** * ZMQ接收线程 */ public abstract class ZmqSubThread implements Runnable { /** * ZMQ启动线程数 */ private int ZMQThreadCount = Integer.parseInt("1"); /** * ZMQ接收端口 */ private int ZMQRecvPort; /** * ZMQ监听接收端口 */ private String ZMQRecvIP; private ZMQ.Context context = null; private ZMQ.Socket subSock = null; public ZmqSubThread() { initZMQ(); } public ZmqSubThread(String ZMQRecvIP, int ZMQRecvPort) { this.ZMQRecvIP = ZMQRecvIP; this.ZMQRecvPort = ZMQRecvPort; initZMQ(); } /** * 初始化ZMQ对象 */ public void initZMQ() { if (ZMQRecvIP == null || "".equals(ZMQRecvIP)) { throw new RuntimeException("IP Error!"); } if (ZMQRecvPort == 0) { throw new RuntimeException("Port Error!"); } context = ZMQ.context(ZMQThreadCount); subSock = context.socket(ZMQ.SUB); String ConUri = "tcp://" + ZMQRecvIP + ":" + ZMQRecvPort; subSock.connect(ConUri); subSock.subscribe("".getBytes()); } @Override public void run() { while (true) { try { byte[] recvBuf = subSock.recv(ZMQ.SUB); if (recvBuf == null) { continue; } dealWith(recvBuf); } catch (Exception e) { e.printStackTrace(); } } } /** * 处理接收到数据的抽象方法 */ public abstract void dealWith(byte[] data); }
第四步:测试类
public class Test { public static void main(String[] args) { ZmqSubThread zmqSubThread = new ZmqSubThread("127.0.0.1", 7111) { @Override public void dealWith(byte[] data) { System.out.println(new String(data)); } }; Thread thread = new Thread(zmqSubThread); thread.start(); } }
学习来源:
//源码分析
https://www.jianshu.com/p/facc6a2b42d5
//项目搭建
https://blog.csdn.net/qq_38425719/article/details/106267750
//基础知识
https://blog.csdn.net/weixin_40685388/article/details/81197552
//服务器和客户端搭建
https://blog.csdn.net/qq_35966478/article/details/101854343