消息总线(Message Queue),后文称MQ,是一种跨进程的通信机制,用于上下游传递消息。
在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。
使用了MQ之后,消息发送上游只需要依赖MQ,逻辑上和物理上都不用依赖其他服务。
什么时候不使用消息总线?
既然MQ是互联网分层架构中的解耦利器,那所有通讯都使用MQ岂不是很好?这是一个严重的误区,调用与被调用的关系,是无法被MQ取代的。
MQ的不足是:
举个例子:用户登录场景,登录页面调用passport服务,passport服务的执行结果直接影响登录结果,此处的“登录页面”与“passport服务”就必须使用调用关系,而不能使用MQ通信。
无论如何,记住这个结论:调用方实时依赖执行结果的业务场景,请使用调用,而不是MQ。
MQ它是一个高效的可嵌入库,它解决了大部分应用程序需要解决的问题,变得在网络上有良好的可伸缩性,而没有多少成本。它能在后台线程异步处理I/O。这些线程使用无锁数据结构与应用程序线程进行通信,所以并发.MQ 应用程序不再需要锁、信号量,或其他等待状态。
组件可以动态地来去自如,而MQ 会自动重新连接。这意味着你可以以任何顺序启动组件。你可以创建“面向服务的架构”(SOA),其中的服务可以在任何时间加入和离开网络。
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持(百度百科给出的概述)。我们可以简单的理解:两个应用程序之间需要进行通信,我们使用一个JMS服务,进行中间的转发,通过JMS 的使用,我们可以解除两个程序之间的耦合。
JMS有以下两个优点:
1.Point-to-Point Messaging Domain(点对点通信模型) 消息不可重复消费
模式图:
点对点模式下,应用程序由消息队列(Queue),发送方(Client1,Sends),接收方(Client2,Consumes)组成。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。
每一条消息只能由一个消费者消费,消费不可重复消费;
接收方(消费者)可随时向消费队列请求消费信息,无论发送方(生产者)是否在发送消息;
2.Publish/Subscribe Messaging Domain(发布/订阅通信模型) 消息可以重复消费
模式图:
本文实现消息队列环境如下:
jdk版本:1.7.0_79
ActiveMQ:apache-activemq-5.14.0附下载地址:该版本下载地址
开发工具:Intellij IDEA(企业版)
1.ActiveMQ的启动:
下载完毕并解压后,打开控制台,到解压好的ActiveMQ的bin目录下
输入activemq start
打开浏览器输入localhost:8161
看到如下场景(默认登陆名和登陆密码都为:admin)
2ActiveMQ下JMS编程模型的实现:
管理对象(Administered objects)-连接工厂(Connection Factories)和目的地(Destination)
连接对象(Connections)
会话(Sessions)
消息生产者(Message Producers)
消息消费者(Message Consumers)
消息监听者(Message Listeners)
(1)连接工厂(Connection Factories)
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
其中ActiveMQConnection.DEFAULT_BROKER_URL为默认的activemq设置好的参数,包括用户名,密码以及网络地址(localhost:8161)
也可以如下设置:
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,ActiveMQConnection.DEFAULT_PASSWORD, "tcp://localhost:61616");
参数分别为设置好的用户名,密码,以及映射到的tcp或者直接设置url
(2)从工厂中得到连接对象并启动对象
connection = connectionFactory.createConnection();
connection.start();
(3)设置会话(session)
session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
session从connection对象中得到,第一个参数为false表示为非事务类型;第二个参数为消息的确认类型,Session.AUTO_ACKNOWLEDGE表示消息自动确认。
(4)设置消息目的地(Destination)
Destination destination = session.createQueue("MyQueue");
消息目的地从会话对象session创建,这里实现消费的不可重复消费,所以创建Queue对象,表示消息将发布到MyQueue的队列中。若要实现发布/订阅模式,使用session.createTopic(“name”);
(5)生产者,消费者
生产者:
producer = session.createProducer(destination);
消费者:
consumer = session.createConsumer(destination);
多个消费者的实现
创建一个consumer类,实现Runnable接口,从而在主函数中启动多个消费者。
消费者的代码实现如下:
package com.message;
import java .util.Date;
import java.text.SimpleDateFormat;
import javax.jms.*;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer implements Runnable{
//消费者名称
private String name;
private MessageConsumer consumer;
private Session session;
private Connection connection = null;
//设置发布地址为默认的URL地址
private static final String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL;
public Consumer(String name) throws JMSException{
this.name = name;
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
connection = connectionFactory.createConnection();
connection.start();
//设置第一个参数为true,事务类型数据(应该可以进一步确保不重复消费)
session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
//设置每次处理的最大消息数为1
Destination destination = session.createQueue("MyQueue?consumer.prefetchSize=1");
consumer = session.createConsumer(destination);
}
public boolean receive()throws JMSException {
//设置消息响应等待5s
TextMessage msg = (TextMessage) consumer.receive(5000);
if (msg == null){
System.out.println("消费者"+this.name+"消息队列为空!!!");
return false;
}
else {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String nowTime = df.format(new Date());
System.out.println("消费者"+this.name+"在时刻:"+nowTime+"消费数据:\n"+msg.getText());
return true;
}
}
public void run(){
try{
while(true){
this.receive();
//线程随机等待0-10s的时间
long waitTime = (long)(Math.random()*1000);
try {
Thread.sleep(waitTime);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}catch (JMSException e){
e.printStackTrace();
}
}
public void close() throws JMSException {
consumer.close();
session.close();
connection.close();
}
}
附上生产者代码实现如下:
package com.message;
import java .util.Date;
import java.text.SimpleDateFormat;
import javax.jms.*;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
//一次性生产的消息数量
private static final int PRODUCER_NUMBER = 5;
//设置发布地址为默认的URL地址
private static final String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL;
private Connection connection = null;
private MessageProducer producer;
private Session session;
public Producer() throws JMSException{
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(BROKER_URL);
//创建从工厂连接中得到的对象
connection = connectionFactory.createConnection();
connection.start();
//fasle:参数表示为非事务类型;AUTO_ACNOWLEDGE:消息自动确认
session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("MyQueue");
producer = session.createProducer(destination);
//设置消息非持久化(学习用法)
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
}
public void sendMessage() throws JMSException{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String nowTime = df.format(new Date());
for (int i = 1;i<= PRODUCER_NUMBER;i++){
TextMessage message = session.createTextMessage(" 生产者的消息数据------时刻:"+nowTime+"编号:"+i);
System.out.println("向队列中发送消息:"+i);
producer.send(message);
}
System.out.println("生产者生产5个数据完毕--------");
}
public void close() throws JMSException {
producer.close();
session.close();
connection.close();
}
}
附上主函数调用生产者消费者如下:
package com.message;
import javax.jms.JMSException;
/**
* 创建一个生产者,三个消费者
* 利用activeMQ对消息队列进行学习
*
*/
public class App {
public static void main(String[] args) {
try {
Producer producer = new Producer();
producer.sendMessage();
//创建3个消费者线程
Consumer consumerA = new Consumer("A");
Consumer consumerB = new Consumer("B");
Consumer consumerC = new Consumer("C");
new Thread(consumerA).start();
new Thread(consumerB).start();
new Thread(consumerC).start();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
附控制台输出和网页端:
【1】JMS消息队列–JMS概述