ActiveMQ是什么?
它是完全支持JMS1.1和J2EE1.4规范的JMS Provider实现的消息中间件。通俗的理解:JMS Provider是接口,ActiveMQ是它的一个实现。
MQ解决的主要有三点:(1)解耦(2)异步--程序性能提高(3)可靠
所以如果使用ActiveMQ,一共有三个大端:发送端、接收端、MQ(中间的)
ActiveMQ在Linux上的安装
参考:
演示在Linux-Centos上安装步骤(注意:关于Linux操作首先是关闭防火墙或者添加允许的端口):
(1)需要的gz包:apache-activemq-5.9.0-bin.tar.gz(bin格式的就是编译好的,可以直接进行安装的。如果下载的是源码格式的需要编译之后再安装)
(2) 解压tar zvxf apache-activemq-5.9.0-bin.tar.gz 解压之后的名字是apache-activemq-5.9.0
(3)将解压包移动到指定位置并重命名为activemq-5.9.0 : mv apache-activemq-5.9.0 /usr/activemq-5.9.0/
(4)启动:
进入/usr/activemq-5.9.0/bin目录下: cd /usr/activemq-5.9.0/bin
启动:./activemq start
(5)查看是否已经启动成功了,有两种方式
第一种使用管道的方式:ps -ef | grep activemq
第二种通过查看MQ管理界面的方式(对于访问MQ管理界面提供的端口是8161):http://ip:8161/admin/
ActiveMQ在Windows上的安装
参考:https://blog.youkuaiyun.com/WuLex/article/details/78323811
如果在windows上安装的activeMQ-5.11版本的,对应的jdk1.8版本。
介绍JMS
用下面的图来具体的介绍一下:
从上面的简单的图示中可以看到,有三个端:发送端(消息生产者)、接收端(消息消费者)、MQ中间件。在MQ中的消息是JMS消息。JMS消息分成三部分:
(1)消息头:get/set
(2)消息属性:消息头以外的属性
(3)消息体:封装具体的消息数据
生产者生产消息放进MQ中存储,消费者从MQ中拿消息进行消费。消费的方式有两种:
(1)同步消费:消费端用receive方法接收,没有消息的时候就会阻塞。当启动消费端的时候,控制台会一直启动着不会停止,因为receive是阻塞式的。
(2)异步消费:消费端的消费做一个监听器,时刻监听着。
消息有两种传递域:
(1)point-to-point:PTP点对点
(2)publish/subscribe:pub/sb订阅发布
PTP点对点传递域:
(0)消息存储在MQ中的目的地叫:队列
(1)每个消息只能有一个消费者(短信)
(2)生产者和消费者是面向队列的,时间无关性:生产者发送了消息,消息在MQ中,即使消费者没有在线,等上线的时候也可以从MQ中提取消息。
(3)一般都是先运行发送端,发送消息到队列里面,然后再运行接收端。
pub/sub:订阅发布传递域:
(0)消息存储在MQ中的目的地叫:主题
(1)每个消息可以有多个消费者(订阅)
(2)生产者和消费者时间有关性:生产者A生产了消息放在MQ中,如果消费端B在A发完之后才上线,那么B看不到B上线之前A发的消息,B只能看到B上线之后A发送的消息,这就是生产者和消费者的时间有关性。这实际上是一个缺陷,因为订阅之后如果有一段时间下线了,这段时间段的消息就接收不到了,这是一个缺陷,为了缓解这个缺陷,sub/pub传递域的消息有持久订阅和非持久订阅之分:
(1)非持久订阅:A订阅了主题B,A在中间某一段时间下线了,这段时间的消息即使A上线了也看不到
(2)持久订阅:A订阅了主题B,A在中间某一段时间下线了,这段时间的消息在A上线之后可以看到
所以在sub/pub传递域中使用持久订阅可以减轻生产者和消费者之间的时间相关性的缺陷。
(3)一般都是先运行接收端再运行发送端。
注意:pub/sub传递域的一个大前提是订阅,如果没有订阅肯定无论如何都收不到消息,订阅之后的消息消费是分为持久订阅和非持久订阅的。
PTP点对点传递域使用MQ的小例子:
(1)创建一个Maven project,不需要勾选"Create a simple project (skip archetype selection)",继续,选择maven-archetype-quickstart(一个简单的模板),
(2)在pom.xml中添加依赖
找Maven依赖的地址:http://maven.aliyun.com/mvn/search
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4.3</version>
</dependency>
(3)创建点对点的生产者
需要的包javax.jms/org.apache.activemq.XXXX
package TestPTP1;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
//PTP模式的发送端
public class QueueSender {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection con = connectionFactory.createConnection();
con.start();
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageProducer producer = session.createProducer(destination);
for(int i=0;i<3;i++){
TextMessage message = session.createTextMessage("message--"+i);
message.setStringProperty("stringProperty"+i, "property1"+i);
producer.send(message);
}
session.commit();
session.close();
con.close();
}
}
解释方法中的参数:
(1)connectionFactory就是连接上ActiveMQ服务,参数:tcp://XXXXXX:61616。tcp表示的是协议,61616是ActiveMQ使用的端口。
(2)connection是根据工程得到一个连接
(3)start()就是连接上,就是发送方与ActiveMQ连接上。
(4)session是一次会话,两个参数以后再讲
(5)destination是目的地,里面的名字是自己随便写的,就是我的消息发送MQ中哪个目的地中
(6)producer是根据会话得到一个生产者,就是一个发送消息的人。
(7)for循环中是循环发出3条消息。
可以把自己写的目的地my-queue当成一个数据库,一个个的消息对象是TextMessage都发送到my-queue消息数据库中的。实际上上面的代码应该用try-catch-finally,finally中是用来关闭资源的。ActiveMQ我理解的实际上就是一个存储消息的队列。
启动MQ的服务:
运行发送端,从MQ管理界面上localhost:8161/admin/ (用户名和密码都是admin)可以看到MQ Message总共是3条,Enqueue进队的消息是3条:
(4)创建点对点的消费者
package TestPTP1;
import java.util.Enumeration;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
//PTP模式的接收端
public class QueueReceiver {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection con = connectionFactory.createConnection();
con.start();
Enumeration jmsxPropertyNames = con.getMetaData().getJMSXPropertyNames();
while(jmsxPropertyNames.hasMoreElements()){
String element = (String)jmsxPropertyNames.nextElement();
System.out.println("jms name="+element);
}
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageConsumer consumer = session.createConsumer(destination);
int i =0;
while(i<3){
TextMessage message = (TextMessage)consumer.receive();
session.commit();
System.out.println("接收到的消息:"+message.getText()+"--接收到的属性:"+message.getStringProperty("stringProperty"+i));
i++;
}
session.close();
con.close();
}
}
创建Session的时候传入了两个参数:
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
第一个参数表示:是否使用事务
第二个参数表示:MQ中的消息被消费之后是否自动通知MQ进行消息的清除(要结合消费端每次receive之后的commit)
解释:
- connection.start()和之前的和发送方是一样的,就是连接上。
- session也是得到会话
- 接收方接受的队列和发送方发送的目的地应该是一样的,my-queue。
- 这边创建的是接受者customer。
为什么消费端每接受一次消息就commit?可以看到session.commit(),每receive一次就会提交一次,而发送方是把所有Message都发送完了才提交的,注意区别。接收端这里recive是接受消息,每次接受完了之后commit,解释commit的原因:消费完之后的消息在MQ队列中应该清除掉,否则会被重复消费,所以commit的作用是在MQ中清除掉已经消费的消息。所以接收端每次recive之后都进行一次commit。
运行消费端的代码,从控制台可以看到消费的消息:
jms name=JMSXUserID
jms name=JMSXGroupID
jms name=JMSXGroupSeq
jms name=JMSXDeliveryCount
jms name=JMSXProducerTXID
接收到的消息:message--0--接收到的属性:property10
接收到的消息:message--1--接收到的属性:property11
接收到的消息:message--2--接收到的属性:property12
从MQ界面可以看到消费后的消息在MQ消息队列Pending Messages中没有没有了,Dequeued出队的消息数就是消费的消息数。
针对上面点对点的例子解释JMS消息
JMS消息包括消息头、消息属性、消息体。
1.消息头包含的属性:
(1)JMSDestination:由send方法设置。消息发送的目的地:Queue和Topic:
producer.send(destination, message)
(2)JMSDeliveryMode:由send方法设置。指的是传送模式:
producer.send(message, deliveryMode, priority, timeToLive)
(3)JMSExpiration:由send方法设置。消息过期时间:
producer.send(message, deliveryMode, priority, timeToLive)
(4)JMSPriority:由send方法设置。消息优先级:
producer.send(message, deliveryMode, priority, timeToLive)
(5)JMSTimestamp:消息的时间戳(消息被发送过去的时候会带一个时间戳):
(6)JMSReplyTo:比如加急的邮件,我希望我发完之后等着能接收到对方的一个回应,就用到下面了(因为MQ是异步的,用这种方式来实现类似有点同步的方式):
(7)JMSRedeliverd消息是否被重复接收:
2.消息体
3.消息属性
包括以下两种类型:
JMS定义的属性:
消息的属性使用的例子:
(1)发送端
package TestPTP1;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
//PTP模式的发送端
public class QueueSender {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection con = connectionFactory.createConnection();
con.start();
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageProducer producer = session.createProducer(destination);
for(int i=0;i<3;i++){
TextMessage message = session.createTextMessage("message--"+i);
message.setStringProperty("stringProperty"+i, "property1"+i);
producer.send(message);
}
session.commit();
session.close();
con.close();
}
}
(2)接收端
package TestPTP1;
import java.util.Enumeration;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
//PTP模式的接收端
public class QueueReceiver {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Connection con = connectionFactory.createConnection();
con.start();
Enumeration jmsxPropertyNames = con.getMetaData().getJMSXPropertyNames();
while(jmsxPropertyNames.hasMoreElements()){
String element = (String)jmsxPropertyNames.nextElement();
System.out.println("jms name="+element);
}
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageConsumer consumer = session.createConsumer(destination);
int i =0;
while(i<3){
TextMessage message = (TextMessage)consumer.receive();
session.commit();
System.out.println("接收到的消息:"+message.getText()+"--接收到的属性:"+message.getStringProperty("stringProperty"+i));
i++;
}
session.close();
con.close();
}
}
JMS的可靠性机制
看下面的例子,解释一下:(1)客户接收消息:receive处
(2)客户处理消息:System处
(3)消息被确认(两点):创建session时的第二个参数(签收的意思)、每次receive之后的commit
消息被处理之后不能结束,还要保证消息数被确认,因为消息被确认之后MQ中对应的消息才能被清除,防止多次被消费。
消息的临时目的地
消息的临时目的地是和session关联的,有时间性的关联,是为了在发送方发送之后可以立刻收到对方的回复而设定的:
(1)发送方:
(2)接收端,接收端根据message.getJMSReplyTo得到临时的目的地,然后在里面添加回复的消息。
JMS的PTP和Pub/Sub
1.PTP的特点
(1)如果消息被接收了但是没有被commit(签收)session就关闭了,那么等下次连接的时候,接收端如果连上了相同的MQ队列,那么消息还会再次被接收。因为没有签收消息就还在MQ中,所以session每次receive之后要commit才能close。可以这样理解,消费端receive消息之后要通知MQ一声,这样MQ才能把已经消费的消息消除掉。
(2)receive接收可以过滤消息,接收自己像接收的消息。
(3)MQ中的消息如果没有设置过期时间就会永久的存储,如果设置了过期时间,还没有被消费的话就会在MQ中被自动的消除。
(4)先启动发送端
2.Pub/Sub的特点
(1)消息订阅分为持久性订阅和非持久性订阅
非持久订阅:消费者订阅了某个主题,如果中间某个时间消费者下线了,下线的这段时间就接收不到消息了,等消费者连接上线之后也收不到主题之前发送的消息。
持久订阅:同理消费者需要订阅某个主题。为了实现持久性订阅,消费者需要在MQ上注册一个ID,消费者下线的时候消息也会发到MQ中ID用户的消息中,消费者上线之后可以接收之前发送的消息,根据自己的ID从MQ中拿。
(2)receive接收的时候可以过滤消息
(3)非持久订阅的时候不能重新发送或者恢复一个未签收的消息,只有持久性的订阅才可以。
(4)如果消息的丢失能容忍就使用非持久订阅,如果要求消息必须全部接收到就使用持久性订阅。
(5)如果使用Pub/Sub模式,必须先启动消费者的一端,保证消费者先订阅了主题才能进行以后的通信
区分:PTP的destination是queue队列,Pub/Sub的destination是topic主题
理解JMS
对于消息的发送接收、消息的处理是发送端或者接收端来负责的。而JMS只负责进行消息的管理。
注意:PTP 是先运行发送端然后运行接收端
注意:PTP类型的消息是很容易理解的:
发送端发送消息进自己的队列中,哪个消费者上线了,如果从队列中取就可以从里面取到消息,并且消费后的消息就会从队列里面消失。所以这个PTP的方式很简单,不用考虑太多其他的因素。