一、概述:
1.什么是activemq
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。两个系统或两个客户端之间进行消息传送,利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。说人话就是 处理详细的一个服务,当我们的后台服务需要发消息的时候,会把消息发送到MQ服务器,当后台服务需要取数据的时候,就MQ中取数据;作用就是异步化提升性能、降低耦合度、流量削峰。
举个例子 学生找老师回答问题的场景,如果有有100个学生同时找一个老师回答问题,那么老师只能一个一个的解答学生的问题。没有轮到的学生只能等着。而采用的mq的方式呢,就是 学生按照老师的要求(JMS)写好提问的内容,并把它交给班长,由班长交给老师。只要交了问题的学生就可以该干嘛就干嘛了,不用排队等着;老师拿到谁的提问,就处理谁的提问。
2.应用场景举例
1)异步通信
注册时的短信、邮件通知,减少响应时间;
2)应用解耦
信息发送者和消息接受者无需耦合,比如调用第三方;
3)流量削峰
例如秒杀系统;
3.消息模型:
- 队列:Point-to-Point(P2P) --- 点对点(生产者发送一条消息到queue,只有一个消费者能收到)
- 主题:Publish/Subscribe(Pub/Sub)--- 发布订阅(发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息)
4.安装:
二、JMS:
java 实现MQ的规范:
JMS开发步骤;
1.创建一个JMS connectionfactory
2.通过connection factory来创建JMS connection
3.启动JMS connection
4.通过connection创建JMS session
5.创建JMS destination
6.创建JMS producer,或者创建JMS message,并设置destination
7.创建JMS consumer,或者是注册一个JMS message listener
8.发送或者接受JMS message(s)
9.关闭所有的JMS资源(connection, session, producer, consumer等) 。
三、队列Queue
特点:
- 每个消息只能有一个消费者。
- 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,它都可以提取消息。
队列的实现方式如图:
消息生产者代码实现:
1 package org.muses.ssm.utils;
2
3 import javax.jms.Connection;
4 import javax.jms.JMSException;
5 import javax.jms.MessageProducer;
6 import javax.jms.Queue;
7 import javax.jms.Session;
8 import javax.jms.TextMessage;
9
10 import org.apache.activemq.ActiveMQConnectionFactory;
11
12 public class TestActiveMqProducer {
13 private static final String ACTIVEMQ_URL = "tcp://192.168.65.116:61616";
14 private static final String QUEUE_NAME = "queue_01";
15
16 public static void main(String[] args) throws JMSException {
17 // 创建连接工厂,按照给定的URL,采用默认用户名密码
18 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
19 // 通过连接工厂 获取connection 并启动访问
20 Connection conn = activeMQConnectionFactory.createConnection();
21 conn.start();
22 // 创建session会话
23 Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
24 // 创建目的地 (具体是队列还是主题topic)
25 Queue queue = session.createQueue(QUEUE_NAME);
26
27 // 创建消息的生产者
28 MessageProducer messageProducer = session.createProducer(queue);
29
30 for (int i = 0; i < 3; i++) {
31 // 创建消息;可以理解为学生按照要求写好问题
32 TextMessage textMessage = session.createTextMessage("mession-------" + i);
33 // 通过messageProducer 发送给mq
34 messageProducer.send(textMessage);
35 }
36 messageProducer.close();
37 session.close();
38 conn.close();
39 System.out.println("发送消息成功");
40 }
41
42
View Code
当发送者发送代码后,mq队列列表显示3条待处理的消息
消费者代码实现方法一:同步阻塞方式(receive())
订阅者或者接受者调用MessageConsumer的receive()方法来接受消息,receive方法在能够接收到消息之前(或超时之前)将一直阻塞;receive()可设置超时时间;
1 package org.muses.ssm.utils;
2
3 import java.io.IOException;
4
5 import javax.jms.Connection;
6 import javax.jms.JMSException;
7 import javax.jms.MessageConsumer;
8 import javax.jms.Queue;
9 import javax.jms.Session;
10 import javax.jms.TextMessage;
11
12 import org.apache.activemq.ActiveMQConnectionFactory;
13
14 public class TestActiveMqConsumer {
15 private static final String ACTIVEMQ_URL = "tcp://192.168.65.116:61616";
16 private static final String QUEUE_NAME = "queue_01";
17
18 public static void main(String[] args) throws JMSException, IOException {
19 // 创建连接工厂,按照给定的URL,采用默认用户名密码
20 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
21 // 通过连接工厂 获取connection 并启动访问
22 Connection conn = activeMQConnectionFactory.createConnection();
23 conn.start();
24 // 创建session会话
25 Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
26 // 创建目的地 (具体是队列还是主题topic)
27 Queue queue = session.createQueue(QUEUE_NAME);
28
29 // 创建消息的生产者
30 MessageConsumer messageConsumer = session.createConsumer(queue);
31 /**
32 * 同步阻塞方式(receive()) 订阅者或者接受者调用MessageConsumer的receive()方法来接受消息,receive方法在能够接收到消息之前(或超时之前)将一直阻塞;
33 */
34 while (true) {
35 TextMessage textMessage = (TextMessage) messageConsumer.receive();
36 if (textMessage != null) {
37 System.out.println("收到消息:" + textMessage.getText());
38 } else {
39 break;
40 }
41 }
42
43 messageConsumer.close();
44 session.close();
45 conn.close();
46
47 }
48
49
View Code
当发布者发布了消息,然后启动消费者代码,则MQ管理后台显示:此时,观察控制台,发现消费者程序一直处于运行状态。原因是recevice()没有设置超时时间,所以消费者会一直开着。如果receive()方法设置了超时时间,则 消费者程序会在等待超时时间后,关闭消费者程序;
消费者和生产者的队列名称一定要一致
控制台打印:
收到消息:mession-------0
收到消息:mession-------1
收到消息:mession-------2
消费者代码实现方法二:消费者监听模式(异步非阻塞方式(监听器onMessage))
订阅或者接收者通过MessageConsumer的setMessageListener(MessageListener messageListener),注册一个消息监听器, 当消息到达以后,系统自动调用监听器的MessageListener的 onMessage(Message message)方法;程序运行完成后,观察控制台发现,消费者程也是一直没有关闭,一直在监听;
消费者和生产者的队列名称一定要一致
1 package org.muses.ssm.utils;
2
3 import java.io.IOException;
4
5 import javax.jms.Connection;
6 import javax.jms.JMSException;
7 import javax.jms.Message;
8 import javax.jms.MessageConsumer;
9 import javax.jms.MessageListener;
10 import javax.jms.Queue;
11 import javax.jms.Session;
12 import javax.jms.TextMessage;
13
14 import org.apache.activemq.ActiveMQConnectionFactory;
15
16 public class TestActiveMqConsumer {
17 private static final String ACTIVEMQ_URL = "tcp://192.168.65.116:61616";
18 private static final String QUEUE_NAME = "queue_01";
19
20 public static void main(String[] args) throws JMSException, IOException {
21 // 创建连接工厂,按照给定的URL,采用默认用户名密码
22 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
23 // 通过连接工厂 获取connection 并启动访问
24 Connection conn = activeMQConnectionFactory.createConnection();
25 conn.start();
26 // 创建session会话
27 Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
28 // 创建目的地 (具体是队列还是主题topic)
29 Queue queue = session.createQueue(QUEUE_NAME);
30
31 // 创建消息的生产者
32 MessageConsumer messageConsumer = session.createConsumer(queue);
33 /**
34 * 消费者监听模式; 异步非阻塞方式(监听器onMessage) 订阅或者接收者通过MessageConsumer的setMessageListener(MessageListener messageListener)
35 * 注册一个消息监听器, 当消息到达以后,系统自动调用监听器的MessageListener的 onMessage(Message message)方法
36 */
37 messageConsumer.setMessageListener(new MessageListener() {
38
39 @Override
40 public void onMessage(Message message) {
41 if (message != null && message instanceof TextMessage) {
42 TextMessage textMessage = (TextMessage) message;
43 try {
44 System.out.println("收到消息:" + textMessage.getText());
45 } catch (JMSException e) {
46 // TODO Auto-generated catch block
47 e.printStackTrace();
48 }
49 }
50 }
51
52 });
53 // 消费者监听模式 必须要有 System.in.read() ;否则无法消费
54 System.in.read();
55
56 messageConsumer.close();
57 session.close();
58 conn.close();
59
60 }
61
62
View Code
控制台打印:
收到消息:mession-------0
收到消息:mession-------1
收到消息:mession-------2
关于有多个消费者的情况说明(本例为两个)
情况一:先生产3条消息 ,先启动消费者1 再启动消费者2号: 2号没有消费到一条消息,1号消费者消费所有消息
实现方式:
生产3条消息,利用消息生产者代码即可实现;
启动消费者1号:消费者代码实现方式二 启动 即可实现1号消费者,可以用 System.out.println("****我是1号消费者******")来表示;
启动消费者2号:再次费者代码实现方式二 启动 即可实现2号消费者,可以用 System.out.println("****我是2号消费者******")来表示;
情况二:先启动2个消费者,再生产消息;那么就会出现 1号消费者和2号消费者平分消息的情况,例如 1号消费者先启动,2号消费者后启动,再生产3条消息:那么就是 1号消费者消费了2条消息,2号消费者消费了1条消息;如果生产了4条消息,那么1号和2号消费者各消费了2条消息;
实现方式:
启动消费者1号:消费者代码实现方式二 启动 即可实现1号消费者,可以用 System.out.println("****我是1号消费者******")来表示;
启动消费者2号:再次费者代码实现方式二 启动 即可实现2号消费者,可以用 System.out.println("****我是2号消费者******")来表示;
生产3条消息,利用消息生产者代码即可实现;
队列表头说明:
- Name:消息队列的名称。
- Number Of Pending Messages:未被消费的消息数目。
- Number Of Consumers:消费者的数量。
- Messages Enqueued:进入队列的消息 ;进入队列的总消息数目,包括已经被消费的和未被消费的。 这个数量只增不减。
- Messages Dequeued:出了队列的消息,可以理解为是被消费掉的消息数量。在Queues里它和进入队列的总数量相等(因为一个消息只会被成功消费一次),如果暂时不等是因为消费者还没来得及消费。
四、订阅Topic
特点:
- 每个消息可以有多个消费者。
- 生产者和消费者之间有时间上的相关性。
- 订阅一个主题的消费者只能消费自它订阅之后发布的消息。JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求 。持久订阅允许消费者消费它在未处于激活状态时发送的消息。
- 在点对点消息传递域中,目的地被成为队列(queue);在发布/订阅消息传递域中,目的地被成为主题(topic)。
需要先有消费者,再有消息生产者。
运行流程:
消费者代码实现:
1 package org.muses.ssm.utils;
2
3 import java.io.IOException;
4
5 import javax.jms.Connection;
6 import javax.jms.JMSException;
7 import javax.jms.Message;
8 import javax.jms.MessageConsumer;
9 import javax.jms.MessageListener;
10 import javax.jms.Session;
11 import javax.jms.TextMessage;
12 import javax.jms.Topic;
13
14 import org.apache.activemq.ActiveMQConnectionFactory;
15
16 public class TestActiveMqConsumer {
17 private static final String ACTIVEMQ_URL = "tcp://192.168.65.116:61616";
18 private static final String TOPIC_NAME = "TOPIC_NAME_1";
19
20 public static void main(String[] args) throws JMSException, IOException {
21 // 创建连接工厂,按照给定的URL,采用默认用户名密码
22 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
23 // 通过连接工厂 获取connection 并启动访问
24 Connection conn = activeMQConnectionFactory.createConnection();
25 conn.start();
26 // 创建session会话
27 Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
28 // 创建目的地 (具体是队列还是主题topic)
29 Topic topic = session.createTopic(TOPIC_NAME);
30
31 // 创建消息的生产者
32 MessageConsumer messageConsumer = session.createConsumer(topic);
33
34 System.out.println("****我是1号消费者******");
35 messageConsumer.setMessageListener(new MessageListener() {
36
37 @Override
38 public void onMessage(Message message) {
39 if (message != null && message instanceof TextMessage) {
40 TextMessage textMessage = (TextMessage) message;
41 try {
42 System.out.println("收到消息:" + textMessage.getText());
43 } catch (JMSException e) {
44 // TODO Auto-generated catch block
45 e.printStackTrace();
46 }
47 }
48 }
49
50 });
51 System.in.read();
52
53 messageConsumer.close();
54 session.close();
55 conn.close();
56
57 }
58
59
View Code
启动3个消费者:(分别启动3此消费者代码 System.out.println("****我是1/2/3号消费者******")来表示),MQ后台显示如下:
生产者代码实现:
1 package org.muses.ssm.utils;
2
3 import javax.jms.Connection;
4 import javax.jms.JMSException;
5 import javax.jms.MessageProducer;
6 import javax.jms.Session;
7 import javax.jms.TextMessage;
8 import javax.jms.Topic;
9
10 import org.apache.activemq.ActiveMQConnectionFactory;
11
12 public class TestActiveMqTopicProducer {
13 private static final String ACTIVEMQ_URL = "tcp://192.168.65.116:61616";
14 private static final String TOPIC_NAME = "TOPIC_NAME_1";
15
16 public static void main(String[] args) throws JMSException {
17 // 创建连接工厂,按照给定的URL,采用默认用户名密码
18 ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
19 // 通过连接工厂 获取connection 并启动访问
20 Connection conn = activeMQConnectionFactory.createConnection();
21 conn.start();
22 // 创建session会话
23 Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
24 // 创建目的地 (具体是队列还是主题topic)
25 Topic topic = session.createTopic(TOPIC_NAME);
26
27 // 创建消息的生产者
28 MessageProducer messageProducer = session.createProducer(topic);
29
30 for (int i = 0; i < 3; i++) {
31 // 创建消息;可以理解为学生按照要求写好问题
32 TextMessage textMessage = session.createTextMessage("mession-------" + i);
33 // 通过messageProducer 发送给mq
34 messageProducer.send(textMessage);
35 }
36 messageProducer.close();
37 session.close();
38 conn.close();
39 System.out.println("发送消息成功");
40 }
41
42
View Code
启动消息生产者代码:此时,1号消费者接受到3条消息;2号消费者也接收到3条消息,3号消费者也接收到3条消息;
控制台显示
****我是1号消费者****** ****我是2号消费者****** ****我是3号消费者******
收到消息:mession-------0 收到消息:mession-------0 收到消息:mession-------0
收到消息:mession-------1 收到消息:mession-------1 收到消息:mession-------1
收到消息:mession-------2 收到消息:mession-------2 收到消息:mession-------2
MQ管理后台显示:
Topic &Queue对比
| Topic
| queue
|
概要
| Publish Subscribe messaging 发布订阅消息
| Point-to-Point 点对点
|
有无状态
| topic数据默认不落地,是无状态的。
| Queue数据默认会在mq服务器上以文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面。也可以配置成DB存储。 |
完整性保障
| 并不保证publisher发布的每条数据,Subscriber都能接受到。
| Queue保证每条数据都能被receiver接收。
|
消息是否会丢失
| 一般来说publisher发布消息到某一个topic时,只有正在监听该topic地址的sub能够接收到消息;如果没有sub在监听,该topic就丢失了。
| Sender发送消息到目标Queue,receiver可以异步接收这个Queue上的消息。Queue上的消息如果暂时没有receiver来取,也不会丢失。
|
消息发布接收策略
| 一对多的消息发布接收策略,监听同一个topic地址的多个sub都能收到publisher发送的消息。Sub接收完通知mq服务器
| 一对一的消息发布接收策略,一个sender发送的消息,只能有一个receiver接收。receiver接收完后,通知mq服务器已接收,mq服务器对queue里的消息采取删除或其他操作。
|
需要的jar包:
版本需要和 activemq 服务的版本对应,例如,服务端装了5.16.5,那么Java程序端需要用5.16.5的jar包
1 <!-- activemq 需要的依赖 -->
2 <dependency>
3 <groupId>org.apache.activemq</groupId>
4 <artifactId>activemq-all</artifactId>
5 <version>5.16.5</version>
6 </dependency>
7 <dependency>
8 <groupId>org.apache.xbean</groupId>
9 <artifactId>xbean-spring</artifactId>
10 <version>4.21</version>
11 </dependency>
官方文档地址:
https://activemq.apache.org/features
我从来不相信什么懒洋洋的自由。我向往的自由是通过勤奋和努力实现的更广阔的人生。
我要做一个自由又自律的人,靠势必实现的决心认真地活着。