目录
1.ActiveMQ的两种消息模式
2.ActiveMQ的高级特性:死信队列
3.springboot整合ActiveMQ队列两种模式
4.springboot整合ActiveMQ并使用连接池
1.ActiveMQ的两种消息模式
a. 点对点模式(point to point 简称PTP)
点对点模式称之为队列(queue)模式,多个消费之间在上线后轮询消费,且一条消息过来只能被一个消费者消费掉。且并不要求消费者和生产者同时在线。队列模式中的消息默认都是持久化的(持久化:消息会默认会存储到磁盘中,重启mq也不会丢失,直到有消费者正常消费完成后才会删除。持久化的配置可以在代码进行设置)。如果没有消费者会一直等到有消费者上线后消费消息后消息才会删除。
b. 发布订阅模式(Publish & Subscribe,简写为Pub & Sub)
发布订阅模式为主题(topic)模式,消息发布者将消息发送到mq中,mq将以广播的方式将消息通知给所有的订阅者。且消息默认是非持久化的,除非有持久性订阅者的话消息才是持久化的。在这种模型下,发布者和订阅者都彼此不知道对方,这种模式就好比匿名公告板。且订阅者收到的消息于发布者之间存在时间的依赖性。除非订阅者建立一个持久的订阅。例如:对于非订阅订阅者,在发布者发布消息后才上线,那么该订阅者只能收到发布者随后发布的消息,不能收到发布者在该订阅者上线之前发送的消息。且中间如果订阅者掉线了1个小时,再上线后也不能收到中间这一个小时发布者发布的所有消息。只能从上线后才能消费。对于持久订阅者,只要第一次上线并且和发布者建立了连接,那么随后无论订阅者掉线了多长时间,再上线后依然能收到断线这一段时间发布者发布的消息。即如果发布者有持久订阅者,那么发布者只有等所有的订阅者都收到消息后才会把消息删除掉,如果该发布者有持久订阅者,那么再持久订阅者无法消费消息的时候,会把消息先持久化起来(重启mq也不会删除),等持久订阅者上线后且消费完成后才会删除。
2.ActiveMQ的高级特性,死信队列
想一下,如果消息的消费者在收到消息后处理失败,抛出异常会怎样
// destination对应配置类中ActiveMQQueue("springboot.queue")设置的名字
@JmsListener(destination = "springboot.queue")
public void listenQueue(String msg) {
log.info("接收到queue消息:" + msg);
/**
* TODO 根据收到的消息处理业务逻辑
*/
int a = 1/0; // 模拟抛出一个异常
}
例如上面的代码,在接收到到的消息进行业务处理时发生了异常,这种情况下会发生什么呢?
如果在onMessage中或者监听模式中整个业务发生问题,mq会重新在主动推送一遍消息给消费者,在达到一定次数后,如果还没有正常的消费掉,mq就会认为这条消息是“有毒的”,会将消息放到 “死信队列” 中(默认是所有的有读消息都会放在队列ActiveMQ.DLQ中) 。但是这中情况仅仅是在队列点对点模式中发现实在这样的。但是在发布订阅者模式中,在web管理界面看这条消息显示还被消费掉,并且不会再重发。(这个下去大家可以试一下,具体后面了解具体原因后再记录下)。这个可能与消息的签收方式等有关,后面再具体研究下。
配置每个队列的死信消息都分开放到不同的私信队列。在activemq.xml中配置
<policyEntry queue=">" >
<deadLetterStrategy>
<individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true" />
</deadLetterStrategy>
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="1000"/>
</pendingMessageLimitStrategy>
</policyEntry>
3. springboot整合ActiveMQ
springboot版本使用2.2.4,activemq版本使用5.15.9
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
application.yml
server:
servlet:
context-path: /activemq
port: 8080
---
activemq:
url: tcp://127.0.0.1:61616
user: user
password: user
ActiveMQConfig 配置mq信息
package com.example.demo.config;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.RedeliveryPolicy;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerEndpoint;
import org.springframework.jms.connection.SingleConnectionFactory;
import org.springframework.jms.listener.MessageListenerContainer;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
/**
* yangsan
*/
@Configuration
@EnableJms
public class ActiveMQConfig {
@Value("${activemq.url}")
private String url;
@Value("${activemq.user}")
private String user;
@Value("${activemq.password}")
private String password;
/**
* 配置连接信息
* @return
*/
@Bean
public ConnectionFactory connectionFactory(){
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(url);
connectionFactory.setUserName(user);
connectionFactory.setPassword(password);
//设置重发属性 根据需要配置 可以不用
connectionFactory.setRedeliveryPolicy(redeliveryPolicy());
return connectionFactory;
}
/**
* 创建队列 和 Topic
* @return
*/
@Bean
public Queue queue() {
return new ActiveMQQueue("springboot.queue");
}
@Bean
public Queue queue2() {
return new ActiveMQQueue("springboot.queue.2");
}
@Bean
public Topic topic() {
return new ActiveMQTopic("springboot.topic");
}
public RedeliveryPolicy redeliveryPolicy(){
RedeliveryPolicy redeliveryPolicy= new RedeliveryPolicy();
//是否在每次尝试重新发送失败后,增长这个等待时间
redeliveryPolicy.setUseExponentialBackOff(true);
//重发次数,默认为6次,这里设置为10次,-1表示不限次数
redeliveryPolicy.setMaximumRedeliveries(3);
//重发时间间隔,默认为1毫秒,设置为10000毫秒
redeliveryPolicy.setInitialRedeliveryDelay(10000);
//表示没有拖延只有UseExponentialBackOff(true)为true时生效
//第一次失败后重新发送之前等待10000毫秒,第二次失败再等待10000 * 2毫秒
//第三次翻倍10000 * 2 * 2,以此类推
redeliveryPolicy.setBackOffMultiplier(2);
//是否避免消息碰撞
redeliveryPolicy.setUseCollisionAvoidance(true);
//设置重发最大拖延时间360000毫秒 表示没有拖延只有UseExponentialBackOff(true)为true时生效
redeliveryPolicy.setMaximumRedeliveryDelay(360000);
return redeliveryPolicy;
}
/**
* springboot默认只配置queue类型消息,如果要使用topic类型的消息,则需要配置该bean
* 使用非持久订阅者
*/
@Bean
public JmsListenerContainerFactory jmsTopicListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//这里必须设置为true,false则表示是queue类型
factory.setPubSubDomain(true);
return factory;
}
/**
* 持久订阅者配置
* @param connectionFactory
* @return
*/
@Bean
public JmsListenerContainerFactory jmsTopicPersistentListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 必须设置clientId才能生效 必须设置factory.setSubscriptionDurable(true) 必须设置为true
factory.setSubscriptionDurable(true);
factory.setPubSubDomain(true);
factory.setClientId("doorAccessRecord");
return factory;
}
}
Producer 生产者
package com.example.demo.producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.jms.*;
/**
* yangsan
*/
@RestController
public class Producer {
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
private Queue queue;
@Autowired
private Topic topic;
@Autowired
private Queue queue2;
//发送queue类型消息
@GetMapping("/queue")
public void sendQueueMsg(String msg) {
jmsTemplate.convertAndSend(queue, msg);
}
//发送queue类型消息
@GetMapping("/queue2")
public void sendQueue2Msg(String msg) {
jmsTemplate.convertAndSend(queue2, msg);
}
//发送topic类型消息
@GetMapping("/topic")
public void sendTopicMsg(String msg) {
jmsTemplate.convertAndSend(topic, msg);
}
//发送queue类型消息
@GetMapping("/queueMore")
public void sendQueueMsgMore() {
for (int i = 0; i < 9; i++) {
jmsTemplate.convertAndSend(queue, String.valueOf(i));
}
}
}
Consumer 消费者
package com.example.demo.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
import javax.jms.Queue;
/**
* 杨三
*/
@Component
@Slf4j
public class Consumer {
private Log log = LogFactory.getLog(Consumer.class);
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
private Queue queue;
// 接收queue类型消息
// destination对应配置类中ActiveMQQueue("springboot.queue")设置的名字
@JmsListener(destination = "springboot.queue")
public void listenQueue(String msg) {
log.info("接收到queue消息:" + msg);
/**
* TODO 根据收到的消息处理业务逻辑
*/
int a = 1/0; // 模拟抛出一个异常
}
@JmsListener(destination = "springboot.queue.2")
public void listenQueue2(String msg) {
log.info("接收到queue2消息:" + msg);
/**
* TODO 根据收到的消息处理业务逻辑
*/
// int a = 1/0; // 模拟抛出一个异常
}
/**
* 非持久订阅者
* 接收topic类型消息
* destination对应配置类中ActiveMQTopic("springboot.topic")设置的名字
* containerFactory对应配置类中注册JmsListenerContainerFactory的bean名称
* @param msg
*/
@JmsListener(destination = "springboot.topic", containerFactory = "jmsTopicListenerContainerFactory")
public void listenTopic(String msg) {
log.info("topic1 接收到topic消息:" + msg);
int i = 1 / 0;
}
/**
* 持久订阅者
* subscription 订阅者名字
* containerFactory对应配置类中注册 jmsTopicPersistentListenerContainerFactory 的bean名称
* @param msg
*/
@JmsListener(destination = "springboot.topic", containerFactory = "jmsTopicPersistentListenerContainerFactory",subscription = "doorAccessRecord")
public void persistentListenTopic(String msg){
log.info("topic2 接收到topic消息:" + msg);
}
}
4.springboot整合ActiveMQ并使用连接池
在不使用连接池的情况下,ActiveMQ默认每发送一条数据就会创建一个连接,避免broker频繁创建连接,减小服务器开销。应该优先考虑使用ActiveMQ连接池
其次说明下:不同的springboot版本使用连接池需要引入的jar包并不相同, springboot版本使用2.2.4,activemq版本使用5.15.9(测试中发现同样的代码如果activemq版本是5.15.11就会报错“Setting clientID on a used Connection is not allowed”,另外好像5.15.10也有问题,所以建议选择5.15.9的版本)
pom.xml
<!-- MQ相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.messaginghub/pooled-jms -->
<dependency>
<groupId>org.messaginghub</groupId>
<artifactId>pooled-jms</artifactId>
</dependency>
application.yml
server:
servlet:
context-path: /activemq
port: 8080
---
spring:
activemq:
broker-url: tcp://127.0.0.1:61616
user: user
password: user
pool:
# 最大连接数 默认1
max-connections: 3
# 默认false 不使用连接池
enabled: true
# 空闲的连接过期时间,默认30s
idle-timeout: 30000
ActiveMQConfig
package com.example.demo.config;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.RedeliveryPolicy;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
/**
* yangsan
*/
@Configuration
@EnableJms
public class ActiveMQPoolConfig {
/**
* 使用连接池需要注入这个bean
*/
@Autowired
JmsPoolConnectionFactory jmsPoolConnectionFactory;
/**
* 创建队列 和 Topic
* @return
*/
@Bean
public Queue queue() {
return new ActiveMQQueue("springboot.queue");
}
@Bean
public Queue queue2() {
return new ActiveMQQueue("springboot.queue.2");
}
@Bean
public Topic topic() {
return new ActiveMQTopic("springboot.topic");
}
/**
* springboot默认只配置queue类型消息,如果要使用topic类型的消息,则需要配置该bean
* 使用非持久订阅者
*/
@Bean
public JmsListenerContainerFactory jmsTopicListenerContainerFactory(JmsPoolConnectionFactory jmsPoolConnectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(jmsPoolConnectionFactory);
//这里必须设置为true,false则表示是queue类型
factory.setPubSubDomain(true);
return factory;
}
/**
* 持久订阅者配置
* @param jmsPoolConnectionFactory
* @return
*/
@Bean
public JmsListenerContainerFactory jmsTopicPersistentListenerContainerFactory(JmsPoolConnectionFactory jmsPoolConnectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(jmsPoolConnectionFactory);
// 必须设置clientId才能生效 必须设置factory.setSubscriptionDurable(true) 必须设置为true
factory.setSubscriptionDurable(true);
factory.setPubSubDomain(true);
factory.setClientId("doorAccessRecord");
return factory;
}
}
其余的生产者和消费者和上面的一样,主要是如果使用连接池的话,需要注入的factory为 JmsPoolConnectionFactory
最后说一下:知道的越多就会发现不知道的越多 。 后面有最新的会及时在补充下。ActiveMQ里面还有好多的高级特性,例如:虚拟队列等等。后面总结了再往上面记录。