工程建立
我们来新建一个工程,然后导入相关依赖
修改pom.xml导入activeMQ依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chaytech</groupId>
<artifactId>activemq_examples</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--activemq需要的jav包-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.15.9</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<!--下面是junit/log4等通用配置-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<modules>
<module>activemq_demo</module>
</modules>
</project>
JMS概述
编码前,我们先来了解一下JMS规范,什么是JMS呢?JMS(Java Message Service)是Java消息服务的规范,任何消息中间件都需要遵守这种规范,套路都是一样的,例如传统的JDBC开发
JMS开发的基本步骤:
Destination简介
Destination中文翻译就是目的地的意思,简单理解就是我们消息的接收方。
Destination分为两种模式:
- 队列(queue)
- 主题(topic)
在点对点的消息传递域中,目的地被称为队列(queue),点对点消息传递特点如下:
- 每个消息只能有一个消费者,类似于1对1的关系。好比个人快递自己领自己的。
- 消息的生产者和消费者之间没有时间上的相关性。无论消费者在生产者发送消息的时候是否处于运行状态,消费者都可以提取消息。好比我们的发送短信,发送者发送后不见得接收者会即收即看。
- 消息被消费后队列中不会再存储,所以消费者不会消费到已经被消费掉的消息。
在发布订阅消息传递域中,目的地被称为主题(topic),发布/订阅消息传递域的特点如下:
- 生产者将消息发布到topic中,每个消息可以有多个消费者,属于1:N的关系;
- 生产者和消费者之间有时间上的相关性。订阅某一个主题的消费者只能消费自它订阅之后发布的消息。
- 生产者生产时,topic不保存消息它是无状态的不落地,假如无人订阅就去生产,那就是一条废消息,所以,一般先启动消费者再启动生产者。
JMS规范允许客户创建持久订阅,这在一定程度上放松了时间上的相关性要求。持久订阅允许消费者消费它在未处于激活状态时发送的消息。一句话,好比我们的微信公众号订阅
队列消息(Queue)入门案例
队列消息生产者:
package com.chaytech.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息生产者
*
* @author Chency
* @email [email protected]
* @Date 2020/03/21 22:20
*/
public class JmsProducer {
private static final String BROKER_URL = "tcp://192.168.0.166:61616"; // activeMQ服务地址
private static final String QUEUE_NAME = "test_queue_1"; // 消息队列名称
public static void main(String[] args) {
/**
* 根据我们指定activeMQ服务地址来创建activeMQ工厂
* 如果我们自己不指定activeMQ服务地址则默认为本机
*/
ActiveMQConnectionFactory activeMQConnection = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = null;
Session session = null;
MessageProducer messageProducer = null;
try {
// 通过连接工厂,获得connection
connection = activeMQConnection.createConnection();
// 打开连接
connection.start();
/**
* 创建session,得到会话
* 两个参数transacted=事务,acknowledgeMode=确认模式(签收)
*/
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 根据session创建队列
Queue queue = session.createQueue(QUEUE_NAME);
// 创建消息的生产者并指定队列
messageProducer = session.createProducer(queue);
// 通过使用消息生产者,生产消息,发送到MQ的队列里面
for (int i = 1; i <= 3; i++) {
TextMessage textMessage = session.createTextMessage("hello_activeMQ_Server" + i);
messageProducer.send(textMessage);
}
System.out.println("**********msg send sucess**********");
} catch (JMSException e) {
e.printStackTrace();
} finally {
try {
messageProducer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
运行消息生产者代码,然后打开activeMQ控制台,点击Queues
:
Number Of Pending Messages:等待消费的消息,这个是未出队列的数量,公式=总接收数-总出队列数。
Number Of Consumers:消费者数量,消费者端的消费者数量。
Messages Enqueued:进队消息数,进队列的总消息量,包括出队列的。这个数只增不减。
Messages Dequeued:出队消息数,可以理解为是消费者消费掉的数量。
当有一个消息进入这个队列时,等待消费的消息是1,进入队列的消息是1。
当消息消费后,等待消费的消息是0,进入队列的消息是1,出队列的消息是1。
当再来一条消息时,等待消费的消息是1,进入队列的消息就是2。
前面我们讲了怎么来生产消息,下面我们来说一下如何来消费消息。
同步阻塞式消费(receive):
package com.chaytech.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息消费者
*
* @author Chency
* @email [email protected]
* @Date 2020/03/22 10:45
*/
public class JmsConsumer {
private static final String BROKER_URL = "tcp://192.168.0.166:61616"; // activeMQ服务地址
private static final String QUEUE_NAME = "test_queue_1"; // 消息队列名称
public static void main(String[] args) {
/**
* 根据我们指定activeMQ服务地址来创建activeMQ工厂
* 如果我们自己不指定activeMQ服务地址则默认为本机
*/
ActiveMQConnectionFactory activeMQConnection = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = null;
Session session = null;
MessageConsumer messageConsumer = null;
try {
// 通过连接工厂,获得connection
connection = activeMQConnection.createConnection();
// 打开连接
connection.start();
/**
* 创建session,得到会话
* 两个参数transacted=事务,acknowledgeMode=确认模式(签收)
*/
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 根据session创建队列
Queue queue = session.createQueue(QUEUE_NAME);
// 创建消息消费者并指定消费的队列
messageConsumer = session.createConsumer(queue);
while (true) {
/**
* reveive() 一直等待接收消息,在能够接收到消息之前将一直阻塞。 是同步阻塞方式 。和socket的accept方法类似的。
* reveive(Long time) : 等待n毫秒之后还没有收到消息,就是结束阻塞。
* 因为发送消息的时候是TextMessage类型,所以消费消息的时候也要是TextMessage类型,要以一一对应
*/
TextMessage textMessage = (TextMessage) messageConsumer.receive();
if(textMessage == null){
break;
}
System.out.println("消费掉的消息 -> " + textMessage.getText());
}
} catch (JMSException e) {
e.printStackTrace();
} finally {
try {
messageConsumer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
异步监听非阻塞方式消费(MessageListener ):
package com.chaytech.activemq;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息消费者
*
* @author Chency
* @email [email protected]
* @Date 2020/03/22 10:45
*/
public class JmsConsumer {
private static final String BROKER_URL = "tcp://192.168.0.166:61616"; // activeMQ服务地址
private static final String QUEUE_NAME = "test_queue_1"; // 消息队列名称
public static void main(String[] args) {
/**
* 根据我们指定activeMQ服务地址来创建activeMQ工厂
* 如果我们自己不指定activeMQ服务地址则默认为本机
*/
ActiveMQConnectionFactory activeMQConnection = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = null;
Session session = null;
MessageConsumer messageConsumer = null;
try {
// 通过连接工厂,获得connection
connection = activeMQConnection.createConnection();
// 打开连接
connection.start();
/**
* 创建session,得到会话
* 两个参数transacted=事务,acknowledgeMode=确认模式(签收)
*/
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 根据session创建队列
Queue queue = session.createQueue(QUEUE_NAME);
// 创建消息消费者并指定消费的队列
messageConsumer = session.createConsumer(queue);
/**
* 通过监听的方式来消费消息,是以异步非阻塞的方式来消费消息
* 通过messageConsumer 的setMessageListener 注册一个监听器,当有消息发送来时,系统自动调用MessageListener 的 onMessage 方法处理消息
*/
messageConsumer.setMessageListener((message) -> {
if (message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费掉的消息 -> " + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
/**
* 此处是为了不让主线程结束,因为一旦主线程结束了,其他的线程(如此处的监听消息的线程)也都会被迫结束。
* 实际开发中不需要
*/
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
messageConsumer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
前面我们只使用了一个消费者,那如果有多个消费者呢?情况会怎么样,下面就是通过实验得到的结论:
- 当只有一个消费者的时候,此消费者会消费掉所有的消息
- 当有两个消费者A和B的时候,A先启动、B后启动,A会消费掉所有的消息,B将消费不到消息
- 当生产者发布消息之前,已经有多个消费者启动就绪了,正等待消费消息,此时生产者发布多条消息,那么activeMQ会根据消费者的数量,轮询来消费消息,例如A消费一条,B消费一条,轮询下去,直到没有消息可消费
主题消息(Topic)入门案例
主题消息与前面所讲的队列消息代码基本一致,唯一的区别就是将创建队列(createQueue)修改为创建主题(createTopic);
消息生产者:
package com.chaytech.activemq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息生产者
*
* @author Chency
* @email [email protected]
* @Date 2020/03/21 22:20
*/
public class JmsProducer {
private static final String BROKER_URL = "tcp://192.168.0.166:61616"; // activeMQ服务地址
private static final String TOPIC_NAME = "test_topic_1"; // 消息主题名称
public static void main(String[] args) {
/**
* 根据我们指定activeMQ服务地址来创建activeMQ工厂
* 如果我们自己不指定activeMQ服务地址则默认为本机
*/
ActiveMQConnectionFactory activeMQConnection = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = null;
Session session = null;
MessageProducer messageProducer = null;
try {
// 通过连接工厂,获得connection
connection = activeMQConnection.createConnection();
// 打开连接
connection.start();
/**
* 创建session,得到会话
* 两个参数transacted=事务,acknowledgeMode=确认模式(签收)
*/
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 根据session创建主题
Topic topic = session.createTopic(TOPIC_NAME);
// 创建消息的生产者并指定队列
messageProducer = session.createProducer(topic);
// 通过使用消息生产者,生产消息,发送到MQ的队列里面
for (int i = 1; i <= 3; i++) {
TextMessage textMessage = session.createTextMessage("topic_message" + i);
messageProducer.send(textMessage);
}
System.out.println("**********topic msg send sucess**********");
} catch (JMSException e) {
e.printStackTrace();
} finally {
try {
messageProducer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
消息消费者:
package com.chaytech.activemq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* 消息消费者
*
* @author Chency
* @email [email protected]
* @Date 2020/03/22 10:45
*/
public class JmsConsumer {
private static final String BROKER_URL = "tcp://192.168.0.166:61616"; // activeMQ服务地址
private static final String TOPIC_NAME = "test_topic_1"; // 消息队列名称
public static void main(String[] args) {
/**
* 根据我们指定activeMQ服务地址来创建activeMQ工厂
* 如果我们自己不指定activeMQ服务地址则默认为本机
*/
ActiveMQConnectionFactory activeMQConnection = new ActiveMQConnectionFactory(BROKER_URL);
Connection connection = null;
Session session = null;
MessageConsumer messageConsumer = null;
try {
// 通过连接工厂,获得connection
connection = activeMQConnection.createConnection();
// 打开连接
connection.start();
/**
* 创建session,得到会话
* 两个参数transacted=事务,acknowledgeMode=确认模式(签收)
*/
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 根据session创建主题
Topic topic = session.createTopic(TOPIC_NAME);
// 创建消息消费者并指定消费的队列
messageConsumer = session.createConsumer(topic);
/**
* 阻塞式消息消费
* reveive() 一直等待接收消息,在能够接收到消息之前将一直阻塞。 是同步阻塞方式 。和socket的accept方法类似的。
* reveive(Long time) : 等待n毫秒之后还没有收到消息,就是结束阻塞。
* 因为发送消息的时候是TextMessage类型,所以消费消息的时候也要是TextMessage类型,要以一一对应
*/
/* while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive();
if(textMessage == null){
break;
}
System.out.println("消费掉的消息 -> " + textMessage.getText());
}*/
/**
* 通过监听的方式来消费消息,是以异步非阻塞的方式来消费消息
* 通过messageConsumer 的setMessageListener 注册一个监听器,当有消息发送来时,系统自动调用MessageListener 的 onMessage 方法处理消息
*/
messageConsumer.setMessageListener((message) -> {
if (message != null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费掉的消息 -> " + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
/**
* 此处是为了不让主线程结束,因为一旦主线程结束了,其他的线程(如此处的监听消息的线程)也都会被迫结束。
* 实际开发中不需要
*/
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
messageConsumer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
运行消息生产者代码,然后打开activeMQ控制台,点击Topics
:
Topic有多个消费者时,消费消息总数 = 消费者数量*生产消息总数;
当多个消费者订阅同一个主题时,每个消费者都能消费此主题下同等数量的消息;
主题消息只有当你订阅了此主题,你才能消费此消息,如果先生产消息,后订阅消息,那么将消费不到订阅前的消息,只能消费订阅后消息,例如微信公众号,如果你不订阅此公众号,那么你将收不到此公众号推送的消息,只有订阅之后才能收到;
队列消息(Queue)和主题消息(Topic)对比
对比项目 | queue | topic |
---|---|---|
工作模式 | 负载均衡模式,如果当前没有消费者,消息也不会丢弃,如果有多个消费者,一条消息只能被其中一个消费者消费,并且可以有ack信息 | 订阅模式,如果当前没有订阅者,那么此消息将会被丢弃,如果有多个订阅者,那么这些订阅者都能够消费此消息 |
有无状态 | queue数据默认会在mq服务器上以文件形式持久化,activeMQ默认保存在安装目录下的/data/kahadb目录下面,也可配置为数据库存储 | 无状态 |
传递完整性 | 消息不会丢弃 | 如果没有订阅者,消息会被丢弃 |
处理效率 | 由于一条消息只能被一个消费者消费,所以消费者再多,性能也不会受到太大的影响,当然不同的消息协议的处理效率也是有差异的 | 由于消息被消费的数量是由订阅者的数量决定的,所以随着订阅者数量的增加性能会出现明显的降低,并且还有不同消息协议所带来的性能影响 |