一、ActiveMQ简介
1. ActiveMQ
- 消息队列 是指利用 高效可靠 的 消息传递机制 进行与平台无关的 数据交流,并基于 数据通信 来进行分布式系统的集成。
- ActiveMQ是Apache所提供的一个开源的消息系统,完全采用Java来实现,因此,它能很好地支持J2EE提出的JMS(Java Message Service,即Java消息服务)规范。JMS是一组Java应用程序接口,它提供消息的创建、发送、读取等一系列服务。JMS提供了一组公共应用程序接口和响应的语法,类似于Java数据库的统一访问接口JDBC,它是一种与厂商无关的API,使得Java程序能够与不同厂商的消息组件很好地进行通信。
- ActiveMQ是消息队列技术,为解决高并发问题而生
- ActiveMQ生产者消费者模型(生产者和消费者可以跨平台、跨系统)
- ActiveMQ支持如下两种消息传输方式 :
- Queue点对点的模式 生产的消息会放到消息服务器中, 直到消费者将消息消费. 点对点模式即一条消息只能被消费一次,消费的先后顺序为优先连接到生产端.
- Topic发布/订阅模式 先订阅后发布, 生产的消息不会进行保存,也就是说在生产者发布消息是订阅者掉线或者连接不是生产者,那么生产者后续不会重新发布消息补救.模式特点:生产者发布的消息所有订阅者都能够接收,不存在点对点一个消息只能消费一次.
二、Window安装ActiveMQ
下载地址:http://activemq.apache.org/download-archives.html
进入官网可以下载各种版本, 下载自己相应的版本即可; 本文使用的版本时: 5.15.9
window下载是一个zip压缩包,直接解压即可, 打开CMD进入解压的bin目录下执行 activemq.bat start 启动。启动完成后打开浏览器输入地址即可看到控制台。
启动方式: bin目录下 activemq.bat start
访问地址:http://localhost:8161/admin/
默认账号:admin / admin
默认端口:8161 (可自行修改/usr/local/apache-activemq-5.15.9/conf/jetty.xml)
三、SpringBoot 整合ActiveMQ
创建SpringBoot项目,添加POM依赖。这里为了方便只创建了一个项目,正常情况下应该创建两个项目:一个作为生产者,一个作为消费者。
<!--ActiveMq消息中间件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--消息队列连接池-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.16.0</version>
</dependency>
引入POM文件后,接下来设置ActiviteMQ的参数,打开项目的配置文件:application.yml或者application.properires;
spring:
session:
store-type: none
jms:
pub-sub-domain: true
activemq:
broker-url: tcp://127.0.0.1:61616
user: admin
password: admin
close-timeout: 15s # 在考虑结束之前等待的时间
in-memory: true # 默认代理URL是否应该在内存中。如果指定了显式代理,则忽略此值。
non-blocking-redelivery: false # 是否在回滚回滚消息之前停止消息传递。这意味着当启用此命令时,消息顺序不会被保留。
send-timeout: 0 # 等待消息发送响应的时间。设置为0等待永远。
queue-name: active.queue.tom #点对点模式 消费者
topic-name: active.topic.jerry #发布-订阅模式 主题名称
pool:
enabled: true #true表示使用连接池;false时,每发送一条数据创建一个连接
max-connections: 10 #连接池最大连接数
idle-timeout: 30000 #空闲的连接过期时间,默认为30秒
启动类开启消息对列 @EnableJms
@SpringBootApplication
//开启缓存
@EnableCaching
//开启MQ
@EnableJms
public class WeChatAppApplication {
public static void main(String[] args) {
SpringApplication.run(WeChatAppApplication.class, args);
}
}
初始化ActiveMQ ,配置连接
@Configuration
public class ActiviteMqConfig {
/**
* 提取配置文件参数信息
*
*/
@Value("${spring.activemq.broker-url}")
private String brokerUrl;
@Value("${spring.activemq.user}")
private String username;
@Value("${spring.activemq.topic-name}")
private String password;
@Value("${spring.activemq.queue-name}")
private String queueName;
@Value("${spring.activemq.topic-name}")
private String topicName;
@Bean(name = "queue")
public Queue queue() {
return new ActiveMQQueue(queueName);
}
@Bean(name = "topic")
public Topic topic() {
return new ActiveMQTopic(topicName);
}
@Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(username, password, brokerUrl);
return factory;
}
@Bean
public JmsMessagingTemplate jmsMessageTemplate() {
return new JmsMessagingTemplate(connectionFactory());
}
// 在Queue模式中,对消息的监听需要对containerFactory进行配置
@Bean("queueListener")
public JmsListenerContainerFactory<?> queueJmsListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//是否启用发布订阅模式 true为发布订阅 false为点对点
factory.setPubSubDomain(false);
return factory;
}
//在Topic模式中,对消息的监听需要对containerFactory进行配置
@Bean("topicListener")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//是否启用发布订阅模式 true为发布订阅 false为点对点
factory.setPubSubDomain(true);
return factory;
}
//订阅-发布持久化设置.
@Bean("topicListenertest")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory1(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSubscriptionDurable(true);
factory.setClientId("toptest");
factory.setPubSubDomain(true);
return factory;
}
@Bean("topicListenerzyh")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory3(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSubscriptionDurable(true);
factory.setClientId("topzyh");
factory.setPubSubDomain(true);
return factory;
}
@Bean("topicListenerdog")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory2(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSubscriptionDurable(true);
factory.setClientId("topdog");
factory.setPubSubDomain(true);
return factory;
}
@Bean("topicListenercat")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory0(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSubscriptionDurable(true);
factory.setClientId("topcat");
factory.setPubSubDomain(true);
return factory;
}
}
消息生产者:通过访问不同连接,来发送不同的消息
@RestController
public class MqController {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
@PostMapping("/queue/test")
public String sendQueue(String str) {
for (int i = 0; i < 10; i++) {
//指定要发送到哪个消息队列中, 只有在这个队列的才能进行消费,这里传入的是通过配置文件来创建的消息队列.
this.sendMessage(queue, "queue:" + i);
//也可以自定义
//this.sendMessage(new new ActiveMQQueue("queueName");, "自定义消息队列");
}
return "success";
}
@PostMapping("/topic/default")
public String sendTopicDefault(String str) {
//指定要发送到哪个主题中, 只有订阅了该主题的 才能接收到
//通过自定义指向, 更加灵活.
this.sendMessage(new ActiveMQTopic("com.default"), "default非持久化测试");
return "success";
}
@PostMapping("/topic/test")
public String sendTopic(String str) {
this.sendMessage(new ActiveMQTopic("com.test"), "test持久化测试");
return "success";
}
@PostMapping("/topic/zyh")
public String sendTopic1(String str) {
this.sendMessage(new ActiveMQTopic("com.zyh"), "zyh持久化测试");
return "success";
}
@PostMapping("/topic/dog")
public String sendTopic2(String str) {
this.sendMessage(new ActiveMQTopic("com.dog"), "dog持久化测试");
return "success";
}
@PostMapping("/topic/cat")
public String sendTopic3(String str) {
this.sendMessage(new ActiveMQTopic("com.cat"), "cat持久化测试");
return "success";
}
// 发送消息,destination是发送到的队列,message是待发送的消息
private void sendMessage(Destination destination, final String message) {
jmsMessagingTemplate.convertAndSend(destination, message);
}
}
Queue消费者:
@Component
public class QueueConsumerListener {
//queue模式的消费者 destination 指定要消费哪个消息队列, 就是我们在生产消息时自定义的队列名
//containerFactory 指定生成JMS监听的工厂 也就是消息的监听器 在之前ActiviteMqConfig中设置的
@JmsListener(destination = "${spring.activemq.queue-name}", containerFactory = "queueListener")
public void readActiveQueue(String message) {
System.out.println("queue接受到:" + message);
}
}
Topic的消费者:
@Component
public class TopicConsumerListener {
//topic模式的消费者 非持久化
@JmsListener(destination = MqConstant.DEFAULT, containerFactory = "topicListener")
public void readActiveQueueDefault(String message, TextMessage textMessage) {
System.out.println("topic接受到:default" + message);
}
//topic模式的消费者
@JmsListener(destination = MqConstant.TEST, containerFactory = "topicListenertest")
public void readActiveQueue(String message, TextMessage textMessage) {
System.out.println("topic接受到:test" + message);
}
//topic模式的消费者
@JmsListener(destination = MqConstant.ZYH, containerFactory = "topicListenerzyh")
public void readActiveQueue1(String message, TextMessage textMessage) {
System.out.println("topic接受到:zyh" + message);
}
//topic模式的消费者
@JmsListener(destination = MqConstant.DOG, containerFactory = "topicListenerdog")
public void readActiveQueue2(String message, TextMessage textMessage) {
System.out.println("topic接受到:dog" + message);
}
//topic模式的消费者
@JmsListener(destination = MqConstant.CAT, containerFactory = "topicListenercat")
public void readActiveQueue3(String message, TextMessage textMessage) {
System.out.println("topic接受到:cat" + message);
}
}
/**
*
* @Description: MQ主题常量类
*
*/
public class MqConstant {
public static final String DEFAULT = "com.default";
public static final String TEST = "com.test";
public static final String ZYH = "com.zyh";
public static final String DOG = "com.dog";
public static final String CAT = "com.cat";
}
四:测试
项目启动,登录MQ控制台可以看到Queue下面有一个消费者已经生成。
Topic主题下也出现了 我们自定义的消费者。其他的消费者订阅的持久化主题。这个下面会说到怎么持久化。
使用Postman或者直接在浏览器输入连接生产消息:
Queue正常接收消息,目前我是创建了一个消费者,如果是多消费者情况,消费者会出现竞争,也就是说每个消费者不会全部消费到生产者所产生的消息。一条消息只能被消费一次,消费的先后顺序为消费者优先连接到生产端.
MQ控制台可以看到 当前消费人数:1;消息生产了10个,消费了10个。
生产Topic消息:
控制台可以看到,我们生产的是default这个主题的消息,所以订阅了该主题的com.default的订阅者进行了消费,而其他的订阅者没有去消费。
该模式:先订阅后发布 生产的消息不会进行保存,也就是说在生产者发布消息是订阅者掉线或者连接不是生产者,那么生产者后续不会重新发布消息补救.模式特点:生产者发布的消息所有订阅者都能够接收,不存在点对点一个消息只能消费一次. 如果掉线后想要去重新获取掉线期间的消息,就需要持久化订阅。
五、持久化订阅
持久化订阅者:在Topic消息监听中 给每个订阅者一个标识
factory.setSubscriptionDurable(true); //开启持久化
factory.setClientId(“top”); //设置标识
持久化成功后订阅者在掉线重启后,会自动拉取掉线期间所错过的消息.
点击控制台持久化栏目,我们可以看到我们创建的4个持久化的订阅者都处于活跃状态,也就是在线状态。我们将项目关闭就可以看到订阅者处于离线状态
此时活跃状态变成了离线状态。
我们再看Queue和Topic栏目,可以发现非持久化的消费者数量变成了0,而持久化的还是保持了相应的数量。
也就是说掉线后订阅的主题下依旧展示订阅过该主题的订阅者数量,不会减少.非持久化在订阅者掉线后会减少相应订阅者数量.
个人理解
一个系统可以去订阅多个主题但每个主题都相当于建立一个新的连接,所以ClientID都必须是唯一,而且每个主题都需要简历一个消息监听JmsListenerContainerFactory配置相应的参数,例如是否持久化,以及设置对应的ClientID。
这样的话订阅者就相当于拥有了一个房间,而ClientID就相当于一个门牌号,发布者在发布消息后就相当于把这些信息都放到了房间里,未持久化的订阅者可以理解为他是租借的这个房间,掉线后系统就把这个房间给回收,所以房间内的消息也会消失。持久化订阅者他就是买下了这个房子,就是掉线系统也不会回收,发布者只需要往房间里放消息就行,上线后就一股脑的全部展现在持久化订阅者前面。
测试持久化就需要开两个或者两个以上的项目,一个作为发布者,其他的作为订阅者,发布者持续在发布,订阅者通过开启和重启项目来模拟掉线和重连,看掉线期间的消息是否会推过来。
生产者环境:只需要保留ActiviteMqConfig, MqController
消费者环境:QueueConsumerListener,TopicConsumerListener ,ActiviteMqConfig,MqConstant把这些放到消费者环境即可。配置文件两边还是都需要的。
- 感谢您耐心的阅读,如果本文对你有所帮助,麻烦留个赞,您的支持是我最大的动力,万分感谢!