最近在看关于Spring源码深入解析的书,里面涉及到了MQ,之前在项目中都是和框架整合在一块或者有封装好的拿来用,这里单纯的介绍一下ActionMQ;
首先,什么是Active MQ?
Active MQ是Apache出品,最流行的,能力强劲的开源消息总线。这是百科给的官方介绍。具体点说,Active MQ,即Java消息服务(Java Message Service)应用程序接口,是一款面向消息的中间件API。中间件,顾名思义是用于在两个或者多个应用程序之间,或分布式系统中发送消息,进行异步通信。
那么既然是通信,都有什么通信模式呢?
JMS具有两种通信模式:
1、Point-to-Point Messaging Domain (点对点)
2、Publish/Subscribe Messaging Domain (发布/订阅模式)
在JMS API出现之前,大部分产品使用“点对点”和“发布/订阅”中的任一方式来进行消息通讯。JMS定义了这两种消息发送模型的规范,它们相互独立。任何JMS的提供者可以实现其中的一种或两种模型,这是它们自己的选择。JMS规范提供了通用接口保证我们基于JMS API编写的程序适用于任何一种模型。
(1)、Point-to-Point Messaging Domain(点对点通信模型)
注:点对点,顾名思义,是一个消息发送者对一个消息消费者(一对一关系);而且发送和消费没有时间上的限制,两者相对独立,互不影响;
(2)、Publish/Subscribe Messaging Domain(发布/订阅通信模型)
注:一个消息可以传递个多个订阅者(即:一个消息可以有多个接受方);
发布者与订阅者具有时间约束,针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态;
为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息;
关于消息通信,是异步还是同步呢?
关于消费者消息同步/异步调度的问题:
在AMQ4时,代理(broker)同步或异步调度消息到消费者就是可配置的。现在我们也可以通过uri链接、connection 和 connectionFactory 的形式配置,这种方式的配置可以替代以前那种只能通过在broker端方式的配置。
无论同步或异步发送消息都是非常有意义的,因为针对消费比较快的消费者,我们使用同步(可以减少异步发送消息时产生的上下文切换),针对消费比较慢的消费者,我们使用异步。 同步发送消息的缺点是,对于生产者发送的消息,如果消费者消费的比较慢,那么生产者就会被阻塞。
默认配置是异步发送 (displayatchAsync=true),这种配置也保证了MQ的高性能。
那么Action MQ相对其他中间件技术有什么长足之处呢?
1. 多种语言和协议编写客户端。语言: Java, C, C++, C#, Ruby, Perl, Python, PHP。应用协议: OpenWire,Stomp REST,WSNotification,XMPP,AMQP
2. 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务)
3. 对Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性
4. 通过了常见J2EE服务器(如 Geronimo,JBoss 4, GlassFish,WebLogic)的测试,其中通过JCA 1.5 resource adaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE 1.4 商业服务器上
5. 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA
6. 支持通过JDBC和journal提供高速的消息持久化
7. 支持Ajax
8. 可以很容易得调用内嵌JMS provider,进行测试
9. ActiveMQ速度非常快;一般要比jbossMQ(内嵌在服务器中)快10倍。
那么Action MQ的应用场景有哪些呢?
一般在应用开发中进行解耦和应用通信,当然ActiveMQ 中间件虽然用Java语言编写,但是ActiveMQ 也为C/C++、.NET、Perl、PHP、Python、Ruby 和一些其它语言提供客户端。在你考虑如何集成不同平台不同语言编写应用的时候,ActiveMQ 拥有巨大优势。此外,ActiveMQ 还提供交叉语言功能,该功能整合这种功能,无需使用远程过程调用(RPC)确实是个优势,因为消息协助应用解耦。
针对上面的问题,都有了解答。下面上练习的Demo代码:
首先,去官网http://activemq.apache.org/activemq-5153-release.html下载对应电脑系统的Active MQ版本;然后运行消息服务器即可。
(注:http://localhost:8161/admin/可进行观察;密码:admin;账户admin);
代码分为消息提供者/消费者两部分:
Pom.xml:
import org.apache.activemq.ActiveMQConnection;
import org.apache.`这里写代码片`activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.*;
/**
* Created by MaiBenBen on 2018/4/21.
*/
public class JMSProduce {
private static final Logger logger= LoggerFactory.getLogger(JMSProduce.class);
private static final String USERNAME = ActiveMQConnection.DEFAULT_USER; // 默认连接
private static final String PASSWORD=ActiveMQConnection.DEFAULT_PASSWORD;//默认密码
private static final String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL; // 默认连接地址 为 failover://tcp://localhost:61616
private static final int Num=100;
public static void main(String[] args){
ConnectionFactory connectionFactory; //与JMS服务器连接工厂实例
Connection connection=null; //与JMS服务器的连接
Session session; //连接中所产生的会话-发送、接受消息的线程
Destination destination; //消息的目的地-消息容器
MessageProducer messageProducer; //消息生产者
//ActiveMQConnectionFactory实例化连接工厂对象
connectionFactory=new ActiveMQConnectionFactory(USERNAME,PASSWORD,BROKER_URL) ;
try{
connection=connectionFactory.createConnection();
connection.start();
session=connection.createSession(true,Session.AUTO_ACKNOWLEDGE); //提交任务,自动确认
// destination=session.createQueue("MQFirstQueue");//创建消息队列
destination = session.createTopic("MQThirdTopic"); // 创建主题
messageProducer=session.createProducer(destination);
sendMessage(session,messageProducer);
session.commit();
}catch (JMSException e) {
e.printStackTrace();
}finally {
if(connection!=null){ //这是connection初始化为null的原因
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
private static void sendMessage(Session session, MessageProducer messageProducer) {
for(int i=0;i<Num;i++){
try {
TextMessage textMessage=session.createTextMessage("ActiveMQ 发送的消息 "+i);
messageProducer.send(textMessage);
logger.info("ActiveMQ发送消息是:MQ:{}",i);
} catch (JMSException e) {
logger.error("ActiveMQ发送消息异常:MQ:{}",i);
e.printStackTrace();
}
}
}
}
Consumer1:
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.*;
/**
* Created by MaiBenBen on 2018/4/21.
*/
public class JMSConsumer {
private static final Logger logger= LoggerFactory.getLogger(JMSConsumer.class);
private static final String USERNAME = ActiveMQConnection.DEFAULT_USER; // 默认连接
private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD; // 默认密码
private static final String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL; // 默认连接地址 为 failover://tcp://localhost:61616
public static void main(String args[]) {
ConnectionFactory connectionFactory; //与JMS服务器连接工厂实例
Connection connection=null; //与JMS服务器的连接
Session session; //连接中所产生的会话-发送、接受消息的线程
Destination destination; //消息的目的地-消息容器
MessageConsumer messageConsumer; // 消息消费者
// 实例化连接工厂
connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD, BROKER_URL);
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); // 消费消息不需要事务,自动确认
// destination = session.createQueue("MQFirstQueue"); // 创建消息队列
destination = session.createTopic("MQThirdTopic");//创建消息订阅者
messageConsumer = session.createConsumer(destination); // 创建消息消费者
//循环消费消息
// while (true) {
// TextMessage textMessage = (TextMessage) messageConsumer.receive(100000);// 设置延时为100s
// if (textMessage!=null) { // 接收到消息
// logger.info("消费消息:MQ:{}",textMessage.getText());
// }else {
// break;
// }
// }
messageConsumer.setMessageListener(new Listener());
} catch (JMSException e) {
logger.error("消费消息异常!");
e.printStackTrace();
}
}
}
Consumer2:
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.*;
/**
* Created by MaiBenBen on 2018/4/21.
*/
public class JMSConsumerSecond {
private static final Logger logger= LoggerFactory.getLogger(JMSConsumer.class);
private static final String USERNAME = ActiveMQConnection.DEFAULT_USER; // 默认连接
private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD; // 默认密码
private static final String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL; // 默认连接地址 为 failover://tcp://localhost:61616
public static void main(String args[]) {
ConnectionFactory connectionFactory; //与JMS服务器连接工厂实例
Connection connection=null; //与JMS服务器的连接
Session session; //连接中所产生的会话-发送、接受消息的线程
Destination destination; //消息的目的地-消息容器
MessageConsumer messageConsumer; // 消息消费者
// 实例化连接工厂
connectionFactory = new ActiveMQConnectionFactory(USERNAME, PASSWORD, BROKER_URL);
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); // 消费消息不需要事务,自动确认
// destination = session.createQueue("MQFirstQueue"); // 创建消息队列
destination = session.createTopic("MQThirdTopic");//创建消息订阅者
messageConsumer = session.createConsumer(destination); // 创建消息消费者
//循环消费消息
// while (true) {
// TextMessage textMessage = (TextMessage) messageConsumer.receive(100000);// 设置延时为100s
// if (textMessage!=null) { // 接收到消息
// logger.info("消费消息:MQ:{}",textMessage.getText());
// }else {
// break;
// }
// }
messageConsumer.setMessageListener(new ListenerSecond());
} catch (JMSException e) {
logger.error("消费消息异常!");
e.printStackTrace();
}
}
}
Listener1:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* Created by MaiBenBen on 2018/4/21.
*/
public class Listener implements MessageListener {
private static final Logger logger= LoggerFactory.getLogger(Listener.class);
@Override
public void onMessage(Message message) {
try {
// logger.info("消费消息:MQ:{}",((TextMessage)message).getText());
logger.info("订阅者一消费消息:MQ:{}",((TextMessage)message).getText());
} catch (JMSException e) {
e.printStackTrace();
logger.info("消费消息异常!");
}
}
}
Listener2:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
* Created by MaiBenBen on 2018/4/21.
*/
public class ListenerSecond implements MessageListener{
private static final Logger logger= LoggerFactory.getLogger(Listener.class);
@Override
public void onMessage(Message message) {
try {
// logger.info("消费消息:MQ:{}",((TextMessage)message).getText());
logger.info("订阅者二消费消息:MQ:{}",((TextMessage)message).getText());
} catch (JMSException e) {
e.printStackTrace();
logger.info("消费消息异常!");
}
}
}
点对点演示结果:
订阅者/消费者演示结果:
上述代码分别演示了点对点、订阅者/消费者两种模式,注意:在订阅者/消费者中,需要先进行订阅活动,才能进行发布操作;针对消费消息的情况,写了循环和监听两种形式。这里的代码像创建ConnectionFactory\Connection\Session\MessageProduce\Destination等是十分冗余的,在Spring中完全可以通过配置文件进行完成。但如果你看到了Spring中的JmsTemplate、和DefaultMessageListenerContainer等监听器的源码时,你会发现源码中的干货代码也就是上面纯代码中的部分,只是添加了部分辅助代码和不同情况、异同的策略而已。