环境
java8、Spring/SpringBoot、activemq.5.16.0
Spring集成
一、xml形式配置
1、说明
springMVC和springboot都可以使用
2、引入jar包
<dependencies>
<!-- activemq核心依赖包 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.10.0</version>
</dependency>
<!-- 嵌入式activemq的broker所需要的依赖包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>
<!-- activemq连接池 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.10</version>
</dependency>
<!-- spring支持jms的包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
</dependencies>
3、resources 下加入配置文件
spring-activemq.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启包的自动扫描 -->
<context:component-scan base-package="com.gouzi.activemq.learn"/>
<!-- 配置生产者 -->
<bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<!-- 正真可以生产Connection的ConnectionFactory,由对应的JMS服务商提供 -->
<bean class="org.apache.activemq.spring.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://ip:61616"/>
</bean>
</property>
<property name="maxConnections" value="100"/>
</bean>
<!-- Spring提供的JMS工具类,他可以进行消息发送,接收等 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 传入连接工厂 -->
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 传入目的地 ,可以时队列和 -->
<!-- <property name="defaultDestination" ref="destinationQueue"/>-->
<!-- 消息自动转换器 -->
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
<!-- Spring提供的JMS工具类,他可以进行消息发送,接收等 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 传入连接工厂 -->
<property name="connectionFactory" ref="connectionFactory"/>
<property name="pubSubDomain" value="true" />
<!-- 传入目的地 ,可以时队列和 -->
<!-- <property name="defaultDestination" ref="destinationQueue"/>-->
<!-- 消息自动转换器 -->
<property name="messageConverter">
<bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
</property>
</bean>
<!-- 这个是队列目的地,点对点的Queue -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!-- 通过构造注入Queue名 -->
<constructor-arg index="0" value="spring-active-queue"/>
</bean>
<!-- 这个是主题目的地, 发布订阅的主题Topic-->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-active-topic"/>
</bean>
<!-- 指定目的地的消费者监听容器器 -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!-- 传入连接工厂 -->
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 传入目的地 -->
<property name="destination" ref="destinationQueue"/>
<!-- 消息监听器-->
<property name="messageListener" ref="myQueueMessageListener"/>
<!-- 开启事务,在DefaultMessageListenerContainer中,如果不开启事务并且没有设置签收机制为手动签收的话,是不会开启重试机制的-->
<property name="sessionTransacted" value="true"/>
<!-- 设置多个并发的消费者 -->
<property name="concurrentConsumers" value="2"/>
</bean>
<bean id="jmsTopicContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!-- 传入连接工厂 -->
<property name="connectionFactory" ref="connectionFactory"/>
<!-- 传入目的地 -->
<property name="destination" ref="destinationTopic"/>
<property name="messageListener" ref="myTopicMessageListener"/>
</bean>
</beans>
4、目的地名称接口
可以使用配置文件注入
/**
* 目的地名称接口
* @author xsh
*/
public interface JmsDestinationName {
String SPRING_ACTIVE_QUEUE = "spring-active-queue";
String SPRING_ACTIVE_TOPIC = "spring-active-topic";
}
5、使用模板发送消息
具体使用时队列使用队列模板jmsQueueTemplate,主题使用主题模板jmsTopicTemplate
/**
* @author 公共的发送方法
*/
@Component
public class JmsProducer {
@Resource
private JmsTemplate jmsQueueTemplate;
@Resource
private JmsTemplate jmsTopicTemplate;
/**
* 队列发送
* @param msgJson 消息串
*/
public void queueSend(String msgJson) {
jmsQueueTemplate.send(JmsDestinationName.SPRING_ACTIVE_QUEUE,(session)->session.createTextMessage(msgJson));
}
/**
* 队列延迟发送
* @param msgJson
* @param delay 延迟时间 毫秒
*/
public void queueSend(String msgJson,long delay) {
jmsQueueTemplate.send(JmsDestinationName.SPRING_ACTIVE_QUEUE,(session)-> {
TextMessage textMessage = session.createTextMessage(msgJson);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
return textMessage;
});
}
public void topicSend(String msgJson){
jmsTopicTemplate.send(JmsDestinationName.SPRING_ACTIVE_TOPIC,(session)->session.createTextMessage(msgJson));
}
}
6、消费者监听器
import com.gouzi.activemq.learn.entity.Unit;
import org.springframework.stereotype.Component;
import javax.jms.*;
@Component
public class MyQueueMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
if (message!=null && message instanceof TextMessage) {
try {
String text = ((TextMessage) message).getText();
System.out.println(text);
} catch (JMSException e) {
e.printStackTrace();
}
}
if (message!=null && message instanceof ObjectMessage) {
try {
ObjectMessage objectMessage = (ObjectMessage) message;
Unit object = (Unit) objectMessage.getObject();
System.out.println(object.getName());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
二、SpringBoot配置
1、引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
2、application.yml
server:
port: 8080
spring:
activemq:
broker-url: tcp://ip:61616
user: admin
password: admin
3、配置
package com.gouzi.activemq.learn.bootmq;
import com.gouzi.activemq.learn.mq.JmsDestinationName;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
@Configuration
public class MyMqConfig {
/**
* 创建一个队列
* @return
*/
@Bean(value = "queue")
public Queue destinationQueue() {
return new ActiveMQQueue(MqDestinationName.SPRING_BOOT_QUEUE);
}
/**
* 创建一个主题
* @return
*/
@Bean(value = "topic")
public Topic destinationTopic() {
return new ActiveMQTopic(MqDestinationName.SPRING_BOOT_TOPIC);
}
/**
* 队列的监听容器工厂
* @param connectionFactory
* @return
*/
@Bean
public JmsListenerContainerFactory<?> queueListenerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
/**
* 主题的监听容器工程
* @param connectionFactory
* @return
*/
@Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
}
4、消费者监听
监听使用注解 @JmsListener
可以指定监听的目的地和使用监听容器工程
@Component
public class MqListenter {
/**
* JmsListener 创建一个监听器。目的地指向一个队列,指定使用的容器工程
*/
@JmsListener(destination = MqDestinationName.SPRING_BOOT_QUEUE,containerFactory = "queueListenerFactory")
public void handelQueueMessage(TextMessage textMessage) throws JMSException {
System.out.println("接收到队列消息:"+textMessage.getText());
}
@JmsListener(destination = MqDestinationName.SPRING_BOOT_TOPIC,containerFactory = "topicListenerFactory")
public void handelTopicMessage(TextMessage textMessage) throws JMSException {
System.out.println("接收到topic消息:"+textMessage.getText());
}
}
5、自定义生成消息的组件
需要注意目的地最好传对应的队列或者是主题。
如果穿的是目的地名称字符串,将默认都是队列
@Component
public class MyMqProducer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
public void send(Destination destination, String msgJson) {
jmsMessagingTemplate.convertAndSend(destination,msgJson);
}
public void send(String destination, String msgJson) {
//如果传入入目的地名称,默认是队列
jmsMessagingTemplate.convertAndSend(destination,msgJson);
}
}
三、监听容器类讲解
1、DefaultMessageListenerContainer
(1)介绍
—源码注释翻译
是使用普通的JMS客户端API的消息监听容器变体,特别是,{@code MessageConsumer.receive()}的循环调用接收消息,也允许被事务管理(将消息注册到XA事务中)
被设计用于在本机JMS环境和java环境中工作,在配置上只有极少的不同。
它是一个简单而且功能强大的消息监听容器。在启动时,它开启固定数量的JMS Session 去调用监听,还可以在运行时动态的去适配(不超过最大的设置数)。像{@link SimpleMessageListenerContainer}一样,它的主要优点是低级别的运行时复杂度,特别是对JMS 提供者(provider)最低要求:甚至不需要JMS {@code ServerSessionPool}配置。除此之外,它是完全自我恢复,以防broker暂时不可用,并且允许停止/重启时和运行时改变它的配置。
事实上,{@code MessageListener}执行在由spring的{@link org.springframework.core.task.TaskExecutor TaskExecutor}抽象的异步单元上。默认,在启动时,将创建规定数量的调度任务,根据{@link #setConcurrentConsumers “concurrentConsumers”}的设置。
指定一个替代的 {@code TaskExecutor} 与现有的线程池工程(如javaEE服务的)集成,例如用{@link org.springframework.scheduling.commonj.WorkManagerTaskExecutor CommonJ WorkManager}。对于本机JMS设置,每个监听器线程都将使用
缓存的JMS {@code Session}和{@code MessageConsumer}(只用在失败才会刷新),尽可能有效地使用JMS提供者的资源。
消息的接收和监听器执行会自动封装到一个事务中,是通过{@link org.springframework.transaction.PlatformTransactionManager}的
{@link #setTransactionManager “transactionManager”}属性设置。
通常是在Java EE环境中使用{@link org.springframework.transaction.jta.JtaTransactionManager},结合JNDI支持的JTA-aware JMS {@code ConnectionFactory}使用。注意,如果指定了外部事务管理器,这个监听容器将会自动为每个事务重新获取所有的JMS句柄,以便于所有JavaEE服务(特别是JBoss)的兼容。这个非缓存行为可以通过{@link #setCacheLevel “cacheLevel”} / {@link #setCacheLevelName “cacheLevelName”}属性来覆盖,强制缓存{@code Connection}(或者{@code Session}和{@code MessageConsumer}),即使涉及到外部事务管理器。
通过指定{@link #setMaxConcurrentConsumers “maxConcurrentConsumers”}值大于{@linksetConcurrentConsumers “concurrentConsumers”}值,可以动态地激活并发调用数的伸缩。 因为concurrentConsumers的默认值是1,你可以简单的指定maxConcurrentConsumers的值,如5,这将使当消费负载变高时并发消费者数量动态的扩大到5 ,以及当消费者负载降低时,动态收缩成原先的值。
考虑适应{@link # setIdleTaskExecutionLimit}“idleTaskExecutionLimit”设置控制每个新任务的生命周期,以避免频繁的上下伸缩,特别是如果{@code ConnectionFactory}没有池JMS {@code会话}和/或{@code TaskExecutor}没有线程池(检查你的配置)。注意,动态伸缩只对队列有意义;对于一个主题,您必须使用默认的1个消费者数量,否则您将在同一个节点上多次收到相同的消息。不要使用Spring的{@link org.springframework.jms.connection.CachingConnectionFactory}与动态伸缩结合使用.理想情况下,千万不要将CachingConnectionFactory与消息监听器容器一起使用,因为通常最好是让监听器容器本身在其生命周期内处理适当的缓存。另外,停止和重新启动监听器容器只适用于独立的本地缓存连接,而不适用于外部缓存的连接。
强烈建议设置{@link #setSessionTransacted “sessionTransacted”}为"true"或指定一个外部的{@linksetTransactionManager “transactionManager”} 。见{@link AbstractMessageListenerContainer}
有关确认模式和本机事务选项的详细信息,如以及{@link AbstractPollingMessageListenerContainer} javadoc来获取详细信息关于配置外部事务管理器。注意,对于默认值"AUTO_ACKNOWLEDGE"模式,该容器在监听器执行之前应用自动消息确认,在出现异常时不会重新传递消息。
(2)常用属性说明
connectionFactory :连接工程,必须制定,推荐使用PooledConnectionFactory。正如上面介绍所说不要使用缓存类型的ConnectionFactory,如果使用动态伸缩时。
destination:指定监听目的地
messageListener:指定监听器
sessionTransacted:开启事务,推荐是true。介绍有说。是重试机制开启的一个条件
sessionAcknowledgeMode:签收机制,默认是自动签收。只有设置成手动签收时,才会开启重试机制
concurrentConsumers:并发的消费者数量,默认是1。如果需要可以设置多个并发。主题模式时,必须是1,否则会出现重复消费
maxConcurrentConsumers:最大的并发消费者数量,和concurrentConsumers有关联,介绍中有讲,动态伸缩
public void setConcurrentConsumers(int concurrentConsumers) {
Assert.isTrue(concurrentConsumers > 0, "'concurrentConsumers' value must be at least 1 (one)");
synchronized (this.lifecycleMonitor) {
this.concurrentConsumers = concurrentConsumers;
if (this.maxConcurrentConsumers < concurrentConsumers) {
this.maxConcurrentConsumers = concurrentConsumers;
}
}
}
concurrency:设置最低最高限制的并发消费者数,和concurrentConsumers、maxConcurrentConsumers有关。
/**
* Specify concurrency limits via a "lower-upper" String, e.g. "5-10", or a simple
* upper limit String, e.g. "10" (the lower limit will be 1 in this case).
* <p>This listener container will always hold on to the minimum number of consumers
* ({@link #setConcurrentConsumers}) and will slowly scale up to the maximum number
* of consumers {@link #setMaxConcurrentConsumers} in case of increasing load.
*/
@Override
public void setConcurrency(String concurrency) {
try {
int separatorIndex = concurrency.indexOf('-');
if (separatorIndex != -1) {
setConcurrentConsumers(Integer.parseInt(concurrency.substring(0, separatorIndex)));
setMaxConcurrentConsumers(Integer.parseInt(concurrency.substring(separatorIndex + 1, concurrency.length())));
}
else {
setConcurrentConsumers(1);
setMaxConcurrentConsumers(Integer.parseInt(concurrency));
}
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid concurrency value [" + concurrency + "]: only " +
"single maximum integer (e.g. \"5\") and minimum-maximum combo (e.g. \"3-5\") supported.");
}
}
(3)重试机制开启条件
sessionTransacted=true||sessionAcknowledgeMode=Session.CLIENT_ACKNOWLEDGE
protected void rollbackOnExceptionIfNecessary(Session session, Throwable ex) throws JMSException {
try {
if (session.getTransacted()) {
if (isSessionLocallyTransacted(session)) {
// Transacted session created by this container -> rollback.
if (logger.isDebugEnabled()) {
logger.debug("Initiating transaction rollback on application exception", ex);
}
JmsUtils.rollbackIfNecessary(session);
}
}
else if (isClientAcknowledge(session)) {
session.recover();
}
}
catch (IllegalStateException ex2) {
logger.debug("Could not roll back because Session already closed", ex2);
}
catch (JMSException | RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback error", ex);
throw ex2;
}
}
需注意
异常捕获的是JMSException 、RuntimeException 、Error 及其子类,如果抛出的异常不是这些类型的异常,重试机制是不会进行捕获的。
解决办法
方法一:手动抛出RuntimeException:throw new RuntimeException(“执行出错啦”);或者Error:new Error();
方法二:监听集成SessionAwareMessageListener,在catch中执行session.rollback();
activemq基础知识
MQ相关知识推荐可以看看这篇文章:https://mp.weixin.qq.com/s/xWl2lAviUFKh3XhDBO_BJg
这个篇文章写的也比较好,一些概念解释比较形象。注意一些说的可能不太合理,需要理性合理的看
1、是什么
是消息中间件MQ的落地产品
2、能干什么
(1)解耦,代码与主业务分开,易扩展
(2)削峰,设置流量缓存池,可以让系统根据自身的吞吐能力进行消费,防止大量请求将系统冲垮。
(3)异步,将与主业务没有强关联的业务异步出去,减少等待时间,以提高接口响应速度
(4)高可用,事务、消息的确认机制和可持久化保障了高可用
(5)延迟加载、定时投放
3、基本知识
传输端口:61616(默认)
控制台端口:8161(默认)
4、JMS规范
4.1、什么是JMS
是Java 消息服务,是两个应用直接互相通信的API,它为标准协议和消息服务提供了一套通用接口,包括创建、发送、读取消息等,用于支持java应用开发。在javaEE中,当两个应用程序使用JMS进行通信时,它们之间不是直接相连的,而是通过一个共同的消息收发服务组件关联起来,以达到解耦、削峰、异步的效果。
4.2、 JMS的组成结构
基本术语
连接工厂:JMS ConnectionFactory 连接工厂是客户用来创建连接的对象。
连接:JMS Connection封装了客户与JMS提供者之间的一个虚拟的连接
会话:JMS Session 是生产和消费消息的单线程上下文。会话用于创建消息生产者(producer)、消息消费者(consumer)等。会话提供了一个事务性的上下文,在这个上下文中,一组发送和接收被组合
目的地:用来指定生产者发送消息的目标和消费者接收消息的目标。
队列Queue:是点对点(PTP)消息传递的模式,每个消息只能一个消费者。生产者和消费者之间没有时间上的关联性
主题Topic:是发布/订阅(sub/pub)模式。生产者发布消息时消费者必须存在。开启持久化后就没有这个限制了。只有存在的消费者都可以消费这条消息
<1> JMS Provider JMS提供者,实现JMS接口和规范的消息中间件,也就是我们说的MQ服务器
<2> JMS Producer JMS的消息生产者,消息生产者是由会话创建的一个对象,用于把消息发送到一个目的地
<3> JMS Consumer JMS的消息消费者,消息消费者是由会话创建的一个对象,它用于接收发送到目的地的消息,消息的消费有两种方式:同步消息:通过消费者的receive方法从目的地中显式提取消息、异步消费:消费者注册一个消息监听器,监听目的地的消息。
<4> JMS Message JMS消息,包含消息头、消息属性、消息体
消息头:消息头中可以设置一些消息属性
JMSDestination:消息目的地
JMSDeliveryMode:消息持久化模式
JMSExpiration:消息过期时间
JMSPriority:消息的优先级
JMSMessageID:消息的唯一标识符。如何解决幂等性的关键。
消息属性:除了消息头中的属性,还提供了一些额外的属性,如延迟加载,或者可以加一下自定义属性
消息体:封装具体的消息数据。有五种消息体格式,发送和接收的格式必须一致
TestMessage: 文本类型,普通字符串
ObjectMessage: 对象消息 ,系列化的Java对象
MapMessage: Map类型的消息,key是String类型,value是Java的基本类型
BytesMessage: 二进制消息,可以写入一下二进制数据
StreamMessage:java数据流消息
目前我在工作中只使用到了前两种类型。
相关代码及介绍
public class JmsProduce {
public static final String ACTIVEMQ_URL = "tcp://ip:61616";
//目的地名称
public static final String QUEUE_NAME = "jdbc01";
public static void main(String[] args) throws JMSException {
//1 按照给定的url创建连接工厂,这个构造器采用默认的用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3创建会话session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//4创建目的地(两种:队列/主题)
Queue queue = session.createQueue(QUEUE_NAME);
//5创建消息的生产者
MessageProducer messageProducer = session.createProducer(queue);
//生产者持久化,默认是持久化的,重启后生产者和未消费的消息都还在。如果设置不持久化,则重启后生产者后消息都丢失
// messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//设置存活时间,如果超过则移除队列不传递给消费者
// messageProducer.setTimeToLive(1000*60);
//6通过messageProducer生产3条消息发送到消息队列中
for (int i = 0; i < 3; i++) {
//7创建消息
TextMessage textMessage = session.createTextMessage("msg--" + i);
// 消息持久化,默认是持久化的,但是设置非持久化好没用,应该send默认是去生产者的持久化模式的。设置此是没有用的,底层最终又会将此参数设置成MessageProducer对应的参数
//textMessage.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
//设置延迟加载 http://activemq.apache.org/delay-and-schedule-message-delivery.html
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,1000*10);
//8通过messageProducer发送给mq
messageProducer.send(textMessage);
}
//9关闭资源
messageProducer.close();
session.close();
connection.close();
System.out.println(" **** 消息发送到MQ完成 ****");
}
}
4.3、JMS可靠性机制
4.3.1消息的持久化
(1) 介绍
保证消息只被传送一次和成功使用一次。在生产者生产发送消息后,消息先被持久化到本地文件或者是数据库中,然后发送给消费者,等待消费者成功消费后再将消息移除。保证服务挂了重启以后,未消费的消息还可以推送给消费者。这样肯定是增加了一定的开销,但是提高了可靠性,而可靠性是优先考虑的因素。当然如果没有可靠性要求的话,可以手动设置成不持久化,具体要根据业务来
(2) 队列的持久化
默认是开启的, 如果不需要持久化可以在消息生产者上设置
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
相关代码见JMS简介
(3) 主题的持久化
默认是非持久化的,因为生产者发送消息时,消费者也同时必须在线。
如果持久化之后,只要消费者监听了这个生产者,只要是生产者发送的消息,该消费者都会接受到该消息,不管当时是不是在线。
//生产者
public class JmsProduceTopic {
public static final String ACTIVEMQ_URL = "tcp://ip:61616";
public static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer producer = session.createProducer(topic);
//等先设置持久化后才能启动
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
for (int i = 0; i < 3; i++) {
TextMessage textMessage = session.createTextMessage("topic:" + i);
producer.send(textMessage);
}
producer.close();
session.close();
connection.close();
System.out.println(" **** 消息发送到MQ完成 ****");
}
}
//消费者
public class JmsConsumerTopicListen {
public static final String ACTIVEMQ_URL = "tcp://ip:61616";
public static final String TOPIC_NAME = "topic01";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
//设置客户端Id,向mq服务器注册自己的名称。如实开启持久化这个是必须的
connection.setClientID("gougou");
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
//创建一个topic订阅者对象,开启持久化这个也是必须的
TopicSubscriber subscriber = session.createDurableSubscriber(topic, "gougou");
connection.start();
Message message = subscriber.receive();
while (message!=null) {
if (message!=null && message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
try {
String text = textMessage.getText();
System.out.println("消费者消息:"+text);
} catch (JMSException e) {
e.printStackTrace();
}
}
}
System.in.read();
session.close();
connection.close();
}
}
4.3.2 消息的事务性
消息的事务性也是保证高可用的条件之一
(1)生产者开启事务后,执行commit后,消息才真正被提交,否则是不会提交的。执行rollback方法,消息会进行回滚。
(2)消费者开启事务后,执行commit后,消息才能真正的被消费,否则是不被标记为被消费,会出现重复消费现象。执行rollback方法后,消息进行回滚
(3)生产者的事务和消费者的事务没有关联关系。
//设置为开启事务 Session createSession(boolean transacted, int acknowledgeMode)
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
//发送消息后提交事务,如果不提交事务,消息时不会被消费
session.commit();
//事务回滚,如果发生异常,或者其它业务需求需要回滚时执行
session.rollback();
4.3.3消息的签收机制
消息的签收机制也是高可用的重要保障之一
签收机制有下面几种方式(Session.java中)
(1)AUTO_ACKNOWLEDGE:自动签收,是系统默认的。无需我们做任何操作,系统自动帮我们签收
(2)CLIENT_ACKNOWLEDGE:手动签收,需要我们进行手动签收,执行代码 textMessage.acknowledge();。如果不进行签收会反复消费
(3)DUPS_OK_ACKNOWLEDGE:允许重复消费,多线程下或者多个消费者会消费到同一个消息,这种出现重复消费的情况
(4)SESSION_TRANSACTED:事务下的签收,开启事务的情况下使用该方式。只要开启事务,即使设置的是前三个,也会执行被替换成SESSION_TRANSACTED。当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息会被再次传送。所以可以说:事务优先签收机制
见源码(ActiveMQConnection.java)
@Override
public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException {
checkClosedOrFailed();
ensureConnectionInfoSent();
if (!transacted) {
if (acknowledgeMode == Session.SESSION_TRANSACTED) {
throw new JMSException("acknowledgeMode SESSION_TRANSACTED cannot be used for an non-transacted Session");
} else if (acknowledgeMode < Session.SESSION_TRANSACTED || acknowledgeMode > ActiveMQSession.MAX_ACK_CONSTANT) {
throw new JMSException("invalid acknowledgeMode: " + acknowledgeMode + ". Valid values are Session.AUTO_ACKNOWLEDGE (1), " +
"Session.CLIENT_ACKNOWLEDGE (2), Session.DUPS_OK_ACKNOWLEDGE (3), ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE (4) or for transacted sessions Session.SESSION_TRANSACTED (0)");
}
}
return new ActiveMQSession(this, getNextSessionId(), transacted ? Session.SESSION_TRANSACTED : acknowledgeMode, isDispatchAsync(), isAlwaysSessionAsync());
}
4.3.4 消息过期
默认是永不过期,可以设置多久不消费自动过期。
4.3.5 消息优先级
生产者发送消息时可以设置消息的提交优先级,优先级范围是0-9,默认是4。0优先级最低,9优先级最高。但JMS Provider并不一定保证按照优先级的顺序提交消息
activemq晋级
一、传输协议
1、协议介绍
TCP协议:是activemq默认的协议,它默认端口是61616。在网络传输数据前,必须先序列化数据。可靠性比较高,效率比较高,支持任何平台
NIO协议:是一种性能比较好的传输协议。推荐使用
AMQP协议:一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息中间件设计。不受客户端、开发语言等条件限制
STOMP协议:是流文本定向消息协议。是面向消息的中间件简单的文本传输协议
MQTT协议:是一种轻量级的发布/订阅消息协议。是IBM开发的一种即时通讯协议,有可能成为物联网的重要组成部分
SSL协议: 使用SSL通过TCP进行通信
AUTO:是一种可以自动检测格式的协议,可以自动检测OpenWire,STOMP,AMQP和MQTT。这样就可以为所有4种类型的客户端共享一种传输方式。
具体参考配置参考官网:http://activemq.apache.org/configuring-transports
2、使用NIO协议
activemq默认使用的传输协议是TCP协议。但是一般生产环境中会使用性能更好的NIO(New I/O)协议
修改activemq.xml配置
<broker>
...
<transportConnectors>
<transportConnector name="nio" uri="nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</<transportConnectors>
...
</broker>
可以不删除其他协议的情况下单独加一个nio协议。也可以把其它一些都删掉,只留nio协议
3、auto自动检测协议和nio上启用auto
如果是5.13.0版及以后版本,同时也可以使用auto自动检测协议,即:可以自动检测OpenWire,STOMP,AMQP和MQTT。这样就可以为所有4种类型的客户端***共享***一种传输方式
在nio上启用auto
在中加入
<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:5671?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
修改application.yml的activemq连接地址
spring:
activemq:
# broker-url: tcp://ip:61616
broker-url: nio://ip:5671
user: admin
password: admin
二、消息存储和持久化
1、官方文档
http://activemq.apache.org/persistence
2、介绍
MQ的高可用的保证是:事务、签收和可持久。 可持久是MQ自身的特性,需要第三方插件去实现。
简单的说持久化是保证MQ宕机了,消息不会丢失,重启后消息还存在可以恢复到消息队列中。
ActiveMQ支持的消息持久化机制有JDBC、AMQ、KahaDB和LevelDB。
具体的逻辑是:当发送者将消息发出后,消息中心首先把消息存储到本地数据文件中、内存数据库中或者远程数据库中等,再试图将
消息发给接收者,成功消费后则将消息从存储中删除,失败后则继续尝试发送。消息中心启动后,首先检查指定存储位置是否有未成功发出
的消息,如果有则将这些消息恢复到消息队列中
3、AMQ Message Store
基于文件的存储机制,是5.3之前的默认机制。
是一种可嵌入的事务性消息存储解决方案,具有极高的速度和可靠性。消息命令被写入事务日志(包含滚动数据日志),
这意味着写入速度非常快,并且存储状态很容易恢复.
在5.3开始推荐使用KahaDB。虽然AMQ的速度比KahaDB的速度快,但是KahaDB提供了比AMQ更高的可伸缩性和扩展性
4、KahaDB
基于文件的持久化数据库,是5.4以来的默认存储机制。
优点:使用的文件描述符比AMQ Message Store少,并且恢复速度更快
使用:在activemq.xml文件中修改persistenceAdapter
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
一些其他参数参考官网设置:http://activemq.apache.org/kahadb
存储原理
消息存储使用了一个事务日志记录数据和一个索引文件来存储数据地址
持久化数据存储位置
activemq/data/kahadb文件下
包含了以下文件:db-x.log db.data db.redo lock
db-x.log
是存储消息的数据文件。这个文件的大小已经默认是32M了,可以通过journalMaxFileLength参数修改设置
默认大小。当数据文件存满时,会创建一个新的数据文件。开始的文件名是db.1.log,第二个文件是db.2.log
以此类推。当不再有引用到数据文件中的任何消息时,该文件将被删除或者归档(可以通过archiveDataLogs这个参数进行设置进行删除或者归档,默认是删除)
db.data
该文件包含了持久化的BTree索引,存储了db-x.log消息的存储位置
db.redo
用于进行消息恢复,如果KahaDB消息存储在强制退出后启动,用于恢复BTree索引
lock
文件锁,表示当前获得kahad读写权限的broker
5、JDBC消息存储
将消息数据持久化到MySQL等数据库中,具体有哪些数据库可以使用参考官方文档:http://activemq.apache.org/jdbc-support 。
这种方式性能很差,需要结合Journal使用。是V4版本支持的
5.1持久化到mysql数据库
配置
(1)添加mysql数据库的驱动包到lib文件夹。
下载mysql-connector-java-8.0.21.jar包(https://repo1.maven.org/maven2/mysql/mysql-connector-java/),将jar包上传到activemq/lib文件夹下
切记:activemq不同版本对应的驱动包不一样。activemq.5.16.0版本使用的是jdk8环境,对应的驱动包是mysql-connector-java-8.0.21.jar
(2)修改activemq.xml下的persistenceAdapter
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
说明:有的博客或者课程说第一次得createTableOnStartup=“true”,其实是不需要的,它默认是true。表示启动时是否要创建表
,如果不需要则手动设置成false。
(3)加入mysql的配置(数据库连接池)需将RelaxAutoCommit标志设置为true
数据库连接池5.16默认的是的dbcp2.如果需要更改连接池,在lib下放相应的驱动
在mysql中间一个activemq数据库(名字自取,但需与下面配置一致) 新建的数据库要采用latin1 或者ASCII编码
在 下面加入下面配置
<bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://ip:端口/activemq?relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
重启后,activemq数据库下会生成下面三张表
表说明
ACTIVEMQ_ACKS
存储持久订阅的信息和最后一个持久订阅接收的消息ID
字段 | 注释 |
---|---|
CONTAINER | (container)消息的目的地destination (主键) |
SUB_DEST | 如果是使用Static集群,这个地段会有集群其他系统的信息 |
CLIENT_ID | (client_id)每个订阅者必须有一个唯一的客户端Id用来区分(主键) |
SUB_NAME | 订阅者名称(主键) |
SELECTOR | (selector)选择器,可以选择只消费满足条件的消息。条件可以用自定义属性实现,可支持多属性AND和OR操作 |
LAST_ACKED_ID | 记录消费过的消费ID |
PRIORITY | 优先级,默认是5 |
XID |
ACTIVEMQ_LOCK
用来标记哪个Broker是Master Broker
在集群环境中才有用,只有一个Broker可以获取到消息,称为Master Broker,其他Broker只能是留作备用,如果当前Master Broker挂掉了,其他Broker选取一个作为Master。
字段 | |
---|---|
ID | |
TIME | |
BROKER_NAME | 作为master的Broker的brokerName |
ACTIVEMQ_MSGS
用来存放队列消息的相关信息。消费后消息被删除。
字段 | 注释 |
---|---|
ID | 主键ID |
CONTAINER | container)消息的目的地destination (主键) |
MSGID_PROD | 消息发送者的主键 |
MSGID_SEQ | 发送消息的顺序,JMS的MessageID = MSGID_PROD+MSGID_SEQ |
EXPIRATION | 消息的过去时间,(毫秒) |
MSG | 消息文本,二进制类型 |
PRIORITY | 优先级,0-9,值越大优先级越高 |
XID |
5.2 JDBC Message Store With ActiveMQ Journal
简介
这种方式克服了JDBC存储方式性能的不足,JDBC方式每次发送消息或者是消费掉消息,都要读写删的操作。
ActiveMQ Journal使用了高速缓存写入技术,在activeMQ和Mysql中又隔了一层,它先将消息持久化到日志文件中,等待一段时间再将未消费的消息批量同步到数据库中,当消费者的消费速度能赶上能及时的赶上生产者的生产速度时,Journal能大大减少写入数据库的数据,从而大大的提高了性能。
这种方式说白了就是使用了日志存储+数据库存储
例如,在使用队列时,常见的是消息在发布后不久就被消耗掉了。因此,您可以发布10,000条消息,而只有少量未完成的消息。因此,当我们检查点到JDBC数据库时,通常只有很少的消息实际写入JDBC。即使我们必须将所有消息都写入JDBC,我们仍然可以通过日志获得性能提升,因为我们可以使用大量事务将消息插入JDBC数据库中,从而提高JDBC方面的性能。
修改配置
<persistenceFactory>
<!-- To use a different dataSource, use the following syntax : -->
<journalPersistenceAdapterFactory journalLogFiles="5" dataDirectory="activemq-data" dataSource="#mysql-ds"/>
</persistenceFactory>
三、主从集群配置
1、官网
http://activemq.apache.org/masterslave.html
2、介绍
仅可提供主备方式的高可用的集群,避免单点故障。同一时间只用一个节点在线,也就是选出来的master结点可用,其它结点都是备用结点,当当前master结点宕机之后,zk从备用结点中选举出一个作为主节点
3、三种形式
4、可复制的LevelDB
该方式性能比较好,需要使用zookeeper进行结点选举。而且不支持延迟投递
4.1官方文档
地址:http://activemq.apache.org/replicated-leveldb-store
中文翻译:
4.2工作流程
简单说明:使用zookeeper管理集群,进行master结点选举,选举出一个master结点作为提供服务的结点,其它结点作为备用结点,如果master宕机后,再从备用结点选举出一个master结点提供服务。主从数据复制,可以通过同步复制,也可以进行异步复制。
具体见官方文档,或者官网中文翻译
4.3 搭建和使用
4.3.1. 集群部署规范表
主机 | MQ集群bind端口 | MQ消息tcp端口 | MQ管理控制台端口 | MQ结点安装目录 | zookeeper集群ip端口 |
---|---|---|---|---|---|
127.0.0.1 | tcp://0.0.0.0:0:62621 | 61616 | 8161 | /usr/local/amqcluster/amq01 | 127.0.0.1+2181、2182、2183 |
4.3.2 zookeeper服务
搭建zookeeper服务,最好是提供集群服务,以保障高可用
4.3.3 activemq服务
提供至少三个服务。本次只使用同一台服务不同端口进行模拟,是伪集群。正式环境应该部署到不同的服务器上,这样会少很多配置了,不用管端口了。
1、下载activemq
https://activemq.apache.org/components/classic/download/
2、创建文件夹
mkdir amqcluster
3、上传压缩包到amqcluster下
4、解压
tar -zxvf apache-activemq-5.16.1-bin.tar.gz
5、重名和复制
mv apache-activemq-5.16.1 amq01
cp -r amq01 amq02
cp -r amq01 amq03
6、删除安装包
rm apache-activemq-5.16.1-bin.tar.gz
7、修改配置
所有集群的brokerName必须一样
修改amq01配置
activemq.xml
brokerName="xshmqcluster"
<!--修改持久化配置 -->
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"
zkPath="/activemq/leveldb-stores"
hostname="xshmqcluster"
sync="local_disk"
/>
</persistenceAdapter>
jetty.xml
<!-- 放开远程访问 -->
<property name="host" value="0.0.0.0"/>
修改amq02配置
activemq.xml
brokerName="xshmqcluster"
<!-- 修改tcp端口为 61617 -->
<transportConnector name="openwire" uri="tcp://0.0.0.0:61617?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<!--修改持久化配置 -->
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"
zkPath="/activemq/leveldb-stores"
hostname="xshmqcluster"
sync="local_disk"
/>
</persistenceAdapter>
jetty.xml
<!-- 放开远程访问 -->
<property name="host" value="0.0.0.0"/>
<!-- 修改控制台端口 -->
<property name="port" value="8162"/>
修改amq03配置
activemq.xml
brokerName="xshmqcluster"
<!-- 修改tcp端口为 61618 -->
<transportConnector name="openwire" uri="tcp://0.0.0.0:61618?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<!--修改持久化配置 -->
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"
zkPath="/activemq/leveldb-stores"
hostname="xshmqcluster"
sync="local_disk"
/>
</persistenceAdapter>
jetty.xml
<!-- 放开远程访问 -->
<property name="host" value="0.0.0.0"/>
<!-- 修改控制台端口 -->
<property name="port" value="8163"/>
启动服务
./amq01/bin/activemq start
./amq02/bin/activemq start
./amq03/bin/activemq start
查看zookeeper注册情况
elected有值表示被选为master结点,对外提供服务。address也必须有值
查看对外提供服务的mq结点
lsof -i:8161
lsof -i:8162
lsof -i:8163
有值显示表示提供服务
测试可用性
停掉主节点服务,查看是否重新选举出新的master结点
项目中使用
url地址修改为:
failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)
注意
replicatedLevelDB中的hostname一旦使用不要轻易改变。如果要改变需要删除data中的一些数据,否则一直没有提供的服务
四、插件使用
1、统计插件- StatisticsPlugin
(1)是什么
是统计mq信息的一个插件,和监控页面功能有些类似,只不过监控页面是可视化的,而该插件是获取接口数据的
(2)能干什么
可以获取整体的入队出队情况、如入队数、待处理消息数、消费者数等等
也可以获取到一个目的地中的运行情况统计
也可以做主题topic的统计
(3)配置
在activemq.xml中加入
<broker ...>
<plugins>
<statisticsBrokerPlugin/>
</plugins>
</broker>
(4)使用
本例只是对一个队列目的地进行统计的demo,需要是可以自行封装成一个工具类
其它两种情况的统计请参考官网:官网地址
package com.xsh.mq;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.util.Assert;
import javax.jms.*;
import java.util.Enumeration;
public class ActiveMQUtil {
public static final String ACTIVEMQ_URL = "tcp://ip:61616";
//需要统计的目的地名称
public static final String QUEUE_NAME = "jdbc01";
public static void main(String[] args) throws JMSException {
//1 按照给定的url创建连接工厂,这个构造器采用默认的用户名和密码
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
//2获取连接connection并启动访问
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
//3创建会话session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建一个临时队列
Queue replyTo = session.createTemporaryQueue();
MessageConsumer consumer = session.createConsumer(replyTo);
//需要统计的目的地
Queue testQueue = session.createQueue(QUEUE_NAME);
//创建一个生产者
MessageProducer producer = session.createProducer(null);
//创建一个统计队列
String queueName = "ActiveMQ.Statistics.Destination." + testQueue.getQueueName();
Queue query = session.createQueue(queueName);
//创建消息并发送
Message msg = session.createMessage();
msg.setJMSReplyTo(replyTo);
producer.send(query, msg);
MapMessage reply = (MapMessage) consumer.receive();
Assert.notNull(reply);
Assert.isTrue(reply.getMapNames().hasMoreElements());
//统计信息输出
for (Enumeration e = reply.getMapNames();e.hasMoreElements();) {
String name = e.nextElement().toString();
System.err.println(name + "=" + reply.getObject(name));
}
consumer.close();
session.close();
connection.close();
}
}
五、 异步投递Async Sends
1、官方文档
http://activemq.apache.org/async-sends
中文翻译:https://blog.youkuaiyun.com/xshsjl/article/details/113861941
2、背景
对于一个慢的消费者(slow Consumer),使用同步发送消息可能出现Producer堵塞等情况,慢消费者适合使用异步发送。
重点
activemq默认使用异步发送模式,但是如果明确指定使用同步发送或者是在未使用事务的前提下发送持久化的消息,会变成同步发送
所以如果没有使用事务并且发送的是持久化的消息,每一次都是同步发送,会阻塞producer直到broker返回一个确认,表示消息已经被安全的持久化到磁盘。确认机制提供了消息安全的保障,但同时会阻塞客户端代理很大的延迟。
而使用异步发送,它可以最大化producer端的发送效率,我们可以在发送消息量比较密集的情况下使用异步投递,它可以很大的提高Producer性能,但是需要消耗较多的客户端内存同时也会导致broker端性能消耗增加。而且由于消息阻塞,producer会认为消息都会被成功投递给mq,如果mq宕机,未持久化的消息会丢失,所以它不能有效的确保消息的发送成功,如果要使用异步投递,那就必须容忍消息丢失的可能
3、开启方式
方式一:
cf = new ActiveMQConnectionFactory("tcp://locahost:61616?jms.useAsyncSend=true");
方式二:
((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);
方式三:
((ActiveMQConnection)connection).setUseAsyncSend(true);
4、如何确认发送成功或者是失败
需要使用ActiveMQMessageProducer中的有回调函数的的send(Message message, AsyncCallback onComplete)
messageProducer.send(message, new AsyncCallback() {
@Override
public void onSuccess() {
// 投递成功后会进行确认
}
@Override
public void onException(JMSException e) {
// 投递失败后进入此方法
}
});
六、延迟投递和定时投递
1、官方文档
http://activemq.apache.org/delay-and-schedule-message-delivery
文档翻译:https://blog.youkuaiyun.com/xshsjl/article/details/113873215
2、使用场景举例
酒店自动入住和退房机,要求客人退房后30分钟时再进行退款处理(因为留30分钟进行查房),这是就需要再当住客点击退房按钮后,延迟30分钟再进行退款操作
3、使用
在activemq.xml中
broker头上添加schedulerSupport=“true”
代码中使用:
MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage("test msg");
long delay = 30 * 1000;
long period = 10 * 1000;
int repeat = 9;
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, delay);
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
message.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
producer.send(message);
具体参数说明看文档
七、消息的重试机制
1、官方文档
http://activemq.apache.org/redelivery-policy
中文翻译:TODO
2、出现场景
消息者消费时出现异常后或者没有确认收到消息,将会重新发送消息进行消费
3、触发消息重试机制的要素
(1)开启事务,如果发生异常调用了rollback,或者消费成功后没有commit
(2)开启手动签收,如果没有进行签收
两者满足其一
4、机制和Posion ACK
默认每隔一秒重发一次,总共重试6次,如果还没有被成功消费掉,消费的回个MQ发一个“poison ack”表示这个消息有毒,告诉broker不要再发了。这条消息进入DLQ(死信队列)中
具体属性见文档
八、死信队列
1、官方文档
http://activemq.apache.org/message-redelivery-and-dlq-handling
文档翻译:https://blog.youkuaiyun.com/xshsjl/article/details/113881319
2、作用
一条消息重试n次后(默认6次),将会被标记为有毒的消息(Poison Ack)并被移入死信队列中,开发人员可以通过查看这个队列进行响应处理,比如手动重试或者删除等
九、等幂性(防止消息重复消费)
方案一
利用mysql主键的唯一性,限制比较高
方案二
利用redis,发送消息时,记录到redis中,消费前先查询redis中是否有记录,如果有记录则消费,消费成功后删除redis记录。