一、概述:
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.安装:
https://www.cnblogs.com/lixiuming521125/p/16499787.html
二、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 }
当发送者发送代码后,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 }
当发布者发布了消息,然后启动消费者代码,则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 }
控制台打印:
收到消息: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 }
启动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 }
启动消息生产者代码:此时,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>