一、前言
上一篇博客介绍了用线程池实现异步任务。这一篇博客谈一谈用MQ实现异步任务。MQ的产品有灰常多,像什么MSMQ、activeMQ、RocketMQ、RabbitMQ、kafak等。在此之前先谈一谈对消息队列的理解。
二、MQ
MQ是一种应用程序对应用程序的通讯方法,应用程序通过读写出入队列的消息来进行通信,两者无需建立连接,发布者和消费者无需知道对方的存在。
MQ是生产者--消费者模型的一个代表,一端往消息队列中不断写入消息,而另一端则可以不断读取或订阅队列中的消息。
使用场景:
1、异步处理:在项目中将一些无需及时返回且耗时的操作提取出来,进行异步处理,采用异步处理,将大大节省了服务器的请求响应时间,从而提高了系统的吞吐量。
2、消息推送
三、RabbitMQ特点
RabbitMQ是一个由Erlang语言开发的AMQP的开源实现。
RabbitMQ的特点为:
1、可靠性
RabbitMQ提供了多种技术可以让你在性能和可靠性之间进行权衡,如持久性、传输确认、投递确认。
2、灵活的路由
在消息进行队列之前,通过Exchange来路由消息。对于典型的路由功能,RabbitMQ已经提供了一些内置的Exchange来实现,针对更复杂的路由功能,可以将多个Exchange绑定在一起,也可以通过插件机制来实现自己的Exchange。
3、消息集群
多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
4、高可用
在同一集群中队列可以被镜像到多台机器,使得在部分节点出现问题的情况下,队列仍然可用。
5、多协议
RabbitMQ支持多种消息队列协议。
6、多语言客户端
支持java、.NET、Ruby等
7、管理界面
RabbitMQ提供了一个易用的用户界面,用户可以监控和管理Broker的许多方面。
8、跟踪机制
如果消息异常,RabbitMQ提供了消息跟踪机制,使用者可以找出发生了什么。
9、插件机制
RabbitMQ提供了很多插件,来从多方面进行扩展,也可以编写各种插件。
四、RabbitMQ中的概念模型
- 消息抽象模型
消费者订阅某个队列,生产者创建消息,然后发布到队列中,最后将消息发送到监听的消费者。
- RabbitMQ的基本概念
上图是一个最简单的抽象模型,接下来将展示一个更加详细的模型。RabbitMQ内部结构如下:
1、Message
Message由消息头和消息体组成,消息体是不透明的,消息头是由一些属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
2、Publisher
消息的生产者,也是一个向交换机发布消息的客户端应用程序。
3、Exchange
用来接收生产者发送的消息,并按照一定的规则将这些消息路由给服务器中的队列。
4、Binding
绑定,用于消息队列和交换器之间的路由关联,一个绑定就是将交换器和队列连接起来的路由规则。
5、Queue
用来保存消息直到发送给消费者,它是消息的容器,1个消息可投入一个到多个队列,消息一直在队列里,等待消费者取走。
6、Connection
网络连接,比如一个TCP连接。
7、Channel
信道,或者称之为管道,是一条双向数据流通道,AMQP都是通过信道发出去的,不管是发布消息,订阅队列,还是接受消息,这些动作都是通过信道来完成的。
8、Consumer
消息的消费者,表示一个从消息队列中取的消息的客户端应用程序。
9、Virtual Host
表示一批交换器、消息队列和相关对象。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
10、Broker
表示RabbitMQ服务器实体
五、入门Demo
pom文件添加依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>com.github.sstone</groupId> <artifactId>amqp-client_2.10</artifactId> <version>1.3</version> </dependency>
1、Publisher
public class Publisher { //队列名称 private final static String QUEUE_NAME = "Queue"; public static void main(String[] args) { // 创建连接工厂 ConnectionFactory factory = null; // 建立到代理服务器到连接 Connection connection = null; // 获得通道 Channel channel = null; try { factory = new ConnectionFactory(); //设置用户名和密码 factory.setUsername("guest"); factory.setPassword("guest"); // 设置 RabbitMQ 地址 factory.setHost("localhost"); // 建立到代理服务器到连接 connection = factory.newConnection(); channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null); String message = "hello world,hello world"; channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8")); System.out.println("发送 message[" + message + "] to "+ QUEUE_NAME +" success!"); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } finally { try { // 关闭资源 channel.close(); connection.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } } } }
2、consumer
public class Consumer { //队列名称 private final static String QUEUE_NAME = "Queue"; public static void main(String[] args) { // 创建连接工厂 ConnectionFactory factory = null; // 建立到代理服务器到连接 Connection connection = null; // 获得通道 Channel channel = null; try { factory = new ConnectionFactory(); factory.setUsername("guest"); factory.setPassword("guest"); factory.setHost("localhost"); // 建立到代理服务器到连接 connection = factory.newConnection(); channel = connection.createChannel(); // 1.队列名2.是否持久化,3是否局限与链接,4不再使用是否删除,5其他的属性 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 声明一个消费者,配置好获取消息的方式 QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(QUEUE_NAME, true, consumer); // 循环获取消息 while (true) { // 循环获取信息 // 指向下一个消息,如果没有会一直阻塞 Delivery delivery = consumer.nextDelivery(); String msg = new String(delivery.getBody()); System.out.println("接收 message[" + msg + "] from " + QUEUE_NAME); } } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } catch (ShutdownSignalException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } finally { try { // 关闭资源 channel.close(); connection.close(); } catch (IOException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } } } }
效果图如下: