1. 概述
ActiveMQ是一种开源的基于JMS(Java Message Service)规范的一种消息中间件的实现,ActiveMQ的设计目标是提供标准的,面向消息的,能够跨越多语言和多系统的应用集成消息通信中间件(消息中间件)。
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。我们可以简单的理解:两个应用程序之间需要进行通信,我们使用一个JMS服务,进行中间的转发,通过JMS 的使用,我们可以解除两个程序之间的耦合。
2. 消息中间件的作用
主要作用是消除高并发访问高峰,加快数据传输速度,消息队列的消费者进程从消息队列中获取数据,异步写入数据库,从而也减小了数据库压力。主要解决应用耦合,异步消息,流量削锋等问题。它实现高性能,高可用,可伸缩和最终一致性架构,是大型分布式系统不可缺少的中间件。
3. JMS的优势
Asynchronous(异步):JMS原本就是一个异步的消息服务,客户端获取消息的时候,不需要主动发送请求,消息会自动发送给可用的客户端。
Reliable(可靠性):JMS保证消息只会递送一次。
4. JMS的消息模型
- Point-to-Point Messaging Domain (点对点)
- Publish/Subscribe Messaging Domain (发布/订阅模式)
在JMS API出现之前,大部分产品使用“点对点”和“发布/订阅”中的任一方式来进行消息通讯。JMS定义了这两种消息发送模型的规范,它们相互独立。任何JMS的提供者可以实现其中的一种或两种模型,这是它们自己的选择。JMS规范提供了通用接口保证我们基于JMS API编写的程序适用于任何一种模型。
4.1 Point-To-Point模型
在点对点通信模式中,应用程序由消息队列,发送方,接收方组成。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。模型如下图:
特点:
- 每个消息只要一个消费者。
- 发送者和接收者在时间上是没有时间的约束,也就是说发送者在发送完消息之后,不管接收者有没有接受消息,都不会影响发送方发送消息到消息队列中。
- 发送方不管是否在发送消息,接收方都可以从消息队列中去到消息。
- 接收方在接收完消息之后,需要向消息队列应答成功。
4.2 Publish/Subscribe 模型
在发布/订阅消息模型中,发布者发布一个消息,该消息通过topic传递给所有的客户端。该模式下,发布者与订阅者都是匿名的,即发布者与订阅者都不知道对方是谁。并且可以动态的发布与订阅Topic。Topic主要用于传递消息。模型图如下:
特点:
- 一个消息可以传递个多个订阅者(即:一个消息可以有多个接受方)
- 发布者与订阅者具有时间约束,针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态。
- 为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。
5. JMS的编程模型
- 管理对象(Administered objects)-连接工厂(Connection Factories)和目的地(Destination)
- 连接对象(Connections)
- 会话(Sessions)
- 消息生产者(Message Producers)
- 消息消费者(Message Consumers)
- 消息监听者(Message Listeners)
- Connection Factories
创建Connection对象的工厂,针对两种不同的jms消息模型,分别有QueueConnectionFactory和TopicConnectionFactory两种。可以通过JNDI来查找ConnectionFactory对象。客户端使用一个连接工厂对象连接到JMS服务提供者,它创建了JMS服务提供者和客户端之间的连接。JMS客户端(如发送者或接受者)会在JNDI名字空间中搜索并获取该连接。使用该连接,客户端能够与目的地通讯,往队列或话题发送/接收消息。 - Destination
目的地指明消息被发送的目的地以及客户端接收消息的来源。JMS使用两种目的地,队列和话题。
对于消息生产者来说,它的Destination是某个队列(Queue)或某个主题(Topic);对于消息消费者来说,它的Destination也是某个队列或主题(即消息来源)。可以通过JNDI来查找Destination。 - Connection
表示在客户端和JMS系统之间建立的链接(对TCP/IP socket的包装)。Connection可以产生一个或多个Session。跟ConnectionFactory一样,Connection也有两种类型:QueueConnection和TopicConnection。连接对象封装了与JMS提供者之间的虚拟连接,如果我们有一个ConnectionFactory对象,可以使用它来创建一个连接。 - Session
Session 是我们对消息进行操作的接口,可以通过session创建生产者、消费者、消息等。Session 提供了事务的功能,如果需要使用session发送/接收多个消息时,可以将这些发送/接收动作放到一个事务中。 - Producer
消息生产者由Session创建,用于往目的地发送消息。生产者实现MessageProducer接口,我们可以为目的地、队列或话题创建生产者; - Consumer
消息消费者由Session创建,用于接收被发送到Destination的消息。 - MessageListener
消息监听器。如果注册了消息监听器,一旦消息到达,将自动调用监听器的onMessage方法。EJB中的MDB(Message-Driven Bean)就是一种MessageListener。
6.ActiveMQ安装部署
(1)下载地址:http://activemq.apache.org/components/classic/download/
选择Linux版本:
(2)在Linux后台输入命令上传下载的压缩包:rz -bey
(3)解压缩:tar zxvf apache-activemq-5.15.11-bin.tar.gz
(4)进入apache-activemq-5.15.11/bin目录下,输入启动命令:./activemq start
启动成功为如下界面:
查看ActiveMQ的启动状态:./activemq status
停止ActiveMQ运行:./activemq stop
(5)在浏览器上输入http://127.0.0.1:8161,选择Manage ActiveMQ broker便可进入管理界面
7.代码示例:
ActiveMQ主题发布者代码:
public class ActiveMqPublisher {
//日志对象
private static Logger log = Logger.getLogger(ActiveMqProducer.class);
//activeMQ的ip
private static final String ACTIVEMQ_IP = "127.0.0.1";
//activeMQ的端口,默认为61616
private static final String ACTIVEMQ_PORT = "61616";
//activeMQ的用户名
private static final String ACTIVEMQ_USERNAME = "root";
//activeMQ的密码
private static final String ACTIVEMQ_PASSWORD = "123456";
//连接工厂:JMS用它创建连接
protected ConnectionFactory connectionFactory;
//JMS客户端到producer的连接
protected Connection connection = null;
//会话,一个接收或者发送消息的线程
protected Session session;
//消息的目的地,消息发送给谁
protected Destination destination;
//消息生产者
protected MessageProducer messageProducer;
public ActiveMqPublisher(String topicName) {
//ActiveMQ连接地址
String brokerURL = "failover:(tcp://" + ACTIVEMQ_IP + ":" + ACTIVEMQ_PORT + ")"
//ActiveMQ重连机制,以保证继续正常工
//initialReconnectDelay:默认为10,单位毫秒,表示第一次尝试重连之前等待的时间
//maxReconnectDelay:默认30000,单位毫秒,表示两次重连之间的最大时间间隔
+ "?initialReconnectDelay=1000&maxReconnectDelay=30000";
//实例化连接工厂
connectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_USERNAME, ACTIVEMQ_PASSWORD, brokerURL);
try {
//通过连接工厂获取连接对象
connection = connectionFactory.createConnection();
//启动连接
connection.start();
//创建Session,获取操作连接,第一个参数表示会话是否在事物中执行,第二个参数设定会话的应答模式
session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
//创建消息队列,两种模式:Queue(队列)、Topic(主题)
/*destination = session.createQueue(queueName);*/
destination = session.createTopic(topicName);
//创建消息生产者
messageProducer = session.createProducer(destination);
log.info("创建ActiveMQ生产者成功")
} catch (Exception e) {
e.printStackTrace();
log.error("创建ActiveMQ生产者失败:" + e.getMessage + e.getStackTrace, e);
}
}
/**
* 发送消息
* @param messageInfo
*/
public void sendMessage(String messageInfo) {
try {
//初始化一个mq消息
TextMessage message = session.createTextMessage();
//装载消息内容
message.setText(messageInfo);
//发送消息
messageProducer.send(message);
} catch (Exception e) {
e.printStackTrace();
log.error("ActiveMQ发送消息失败:" + e.getMessage + e.getStackTrace, e);
} finally{
if(connection!=null){
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
log.error("生产端ActiveMQ关闭连接失败:" + e.getMessage + e.getStackTrace, e);
}
}
}
}
public static void main(String[] args) {
ActiveMqPublisher activeMqPublisher = new activeMqProducer("topic");
activeMqPublisher.sendMessage("消息内容");
}
}
ActiveMQ主题订阅者代码:
//1.监听器Listener
public class ActiveMqListener implements MessageListener {
//日志对象
private static Logger log = Logger.getLogger(ActiveMqListener.class);
@Override
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage)message;
String jsonStr = textMessage.getText();
log.info("消费者消费的message:" + jsonStr);
} catch (JMSException e) {
e.printStackTrace();
log.error("ActiveMQ消费异常:" + e.getMessage + e.getStackTrace, e);
}
}
}
//2.ActiveMQ主题订阅者
public class ActiveMqSubscribe {
//日志对象
private static Logger log = Logger.getLogger(ActiveMqProducer.class);
//activeMQ的ip
private static final String ACTIVEMQ_IP = "127.0.0.1";
//activeMQ的端口,默认为61616
private static final String ACTIVEMQ_PORT = "61616";
//activeMQ的用户名
private static final String ACTIVEMQ_USERNAME = "root";
//activeMQ的密码
private static final String ACTIVEMQ_PASSWORD = "123456";
//连接工厂
protected ConnectionFactory connectionFactory;
//连接
protected Connection connection = null;
//会话
protected Session session;
//连接目的地
protected Destination destination;
//消息消费者
protected MessageConsumer messageConsumer;
public ActiveMqSubscribe(String topicName) {
//ActiveMQ连接地址
String brokerURL = "failover:(tcp://" + ACTIVEMQ_IP + ":" + ACTIVEMQ_PORT + ")"
+ "?initialReconnectDelay=1000&maxReconnectDelay=30000";
//实例化工厂
connectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_USERNAME, ACTIVEMQ_PASSWORD, brokerURL);
try {
//通过连接工厂获取连接对象
connection = connectionFactory.createConnection();
//启动连接
connection.start();
//创建Session,获取操作连接,第一个参数表示会话是否在事物中执行,第二个参数设定会话的应答模式
session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE);
//创建消息队列,两种模式:Queue(队列)、Topic(主题)
/*destination = session.createQueue(queueName);*/
destination = session.createTopic(topicName);
//创建消息消费者
messageConsumer = session.createConsumer(destination);
messageConsumer.setMessageListener(new ActiveMqListener());
log.info("创建ActiveMQ消费者监听队列成功");
} catch (JMSException e) {
e.printStackTrace();
log.error("创建ActiveMQ生产者失败:" + e.getMessage + e.getStackTrace, e);
}finally {
try {
if (null != connection) {
connection.close();
}
} catch (JMSException e) {
e.printStackTrace();
log.error("消费端ActiveMQ关闭连接失败:" + e.getMessage + e.getStackTrace, e);
}
}
}
}