ActiveMQ教程以及面试题

目录

消息中间件的引用场景

JMS消息模型:

点对点模型的特点:

发布/订阅模型的特点:

核心API:

使用API创建生产者

使用API创建消费者

JMS消息组成格式

结构:

JMS Message组成

消息头

消息体

消息属性

ActiveMQ的高级特性

ActiveMQ消息持久化

消息事务

消息确认机制

消息投递方式

1、异步投递 VS 同步投递

2、延迟投递

定时投递

死信队列

ActiveMQ企业经典面试问题

ActiveMQ宕机了怎么办?

如何防止消费者消费重复消息?

如何防止消息丢失?

什么是死信队列?


消息中间件的引用场景

异步处理、应用解耦、流量削峰

JMS消息模型:

点对点模型(point to point):即生产者和消费者之间的消息来往;

发布/订阅模型(Pub/Sub):包含三个角色:主题(Topic),发布者(publisher),订阅者(subscriber),多个发布者将消息发送到topic,系统将这些消息投递到订阅此Topic的订阅者;

点对点模型的特点:

a).每个消息只有一个消费者(即一旦被消费,消息就不再消息队列中);

b).发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送消息之后,不管接收者有没有在运行,它不会影响消息被发送到队列;

c).接收者在成功接收消息之后需向队列应带成功;

发布/订阅模型的特点:

a).每个消息可以有多个消费者;

b).发布者和订阅者之间有时间上的依赖(消费者先订阅主题,发布者再来发送消息);

c).订阅者必修保持运行的状态,才能接收发布者发布的消息;

核心API:

       a).Destination:表示消息所走通道的名称;

       b).ConnectionFactory:用于创建连接对象;

      c).Connection:连接接口,用来创建session;

       d).Session:会话接口,这是一个重要的接口,消息发送者、消息接收者以及消息对象本身,都是通过这个会话创建,创建destination;

       e).MessageConsumer:消息的消费者;

       f).MessageProducer:消息的生产者;

使用API创建生产者

创建连接工厂

创建连接

打开链接

创建session会话(两个参数:第一个是是否开启事务,第二个消息的确认机制)

创建destination目的地

创建消息生产者

创建消息

发送消息

释放资源

使用API创建消费者

创建连接工厂

创建连接

打开链接

创建session会话(两个参数:第一个是是否开启事务,第二个消息的确认机制)

创建distination目的地

创建消息消费者

获取消息(使用receive方法)/设置消息监听器来接收消息(不要释放资源)

JMS消息组成格式

结构:

JMS Provider(消息中间件/消息服务器/消息提供者);

JMS Producer(消息生产者);

JMS Consumer(消息消费者);

JMS Message(消息----重要部分)

JMS Message组成

JMS Message消息由三部分组成:

消息头

名称

概述

JMSDestination

消息发送的目的地,在发送过程中由提供者设置;

JMSMessageID

唯一识别每个消息的标识,由提供者产生,客户机只能在消息发送后才能确定消息的JMSMessageID;

JMSDeliveryMode

消息持久化。有两种 :持久模式(DeliveryMode.PERSISTENT)和非持久模式(DeliveryMode.NON_PERSISTENT);

JMSTimestamp

提供者发送消息的时间,由提供者在发送过程中设置;

JMSExpiration

消息过期时间,毫秒单位,值0表明消息不会过期,默认值为0;

JMSPriority

消息优先级,从 0-9 十个级别,0最低,9最高,0-4 是普通消息,5-9 是加急消息。ActiveMQ不要求提供者严格按照这十个优先级发送消息,但必须保证加急消息要先于普通消息发送,默认是4级;

JMSCorrelationID

用来连接到另外一个消息,典型的应用是在回复消息中连接到原消息。在大多数情况下,JMSCorrelationID用于将一条消息标记为对JMSMessageID标示的上一条消息的应答,不过,JMSCorrelationID可以是任何值,不仅仅是JMSMessageID,由开发者设置

JMSReplyTo

提供本消息回复消息的目的地址,由开发者设置

JMSType

消息类型的识别符,由开发者设置

JMSRedelivered

如果一个客户端收到一个设置了JMSRedelivered属性的消息,则表示可能客户端曾经在早些时候收到过该消息,但并没有签收(acknowledged)。消息的重发标识,false,代表第一次发送,true,代表消息为重发消息

只有JMSCorrelationIDJMSReplyTo、JMSType可以由开发者设置的

消息体

消息体,JMS API定义了5种消息体格式,也叫消息类型,可以使用不同形式发送接收数据,并可以兼容现有的消息格式。包括:

TextMessage(字符串)

MapMessage(键值对)

BytesMessage(字节的数据流)

ObjectMessage(序列化的对象)

StreamMessage(java原始值的数据流,可以传递字符串,整型等不同类型的数据,数据生产与消费顺序要一致)

注意:ActiveMQ5.12后,为了安全考虑,ActiveMQ默认不接受自定义的序列化对象,需要将自定义的对象加入到受信任的列表

消息属性

消息属性,包含以下三种类型的属性:

1应用程序自定义设置和添加的属性,比如:Message.setStringProperty("hello", "my name is wangsaichao!");

2:JMS定义的属性:使用“JMSX”作为属性名的前缀,connection.getMetaData().getJMSXPropertyNames(), 方法返回所有连接支持的JMSX 属性的名字。

3:JMS供应商特定的属性:JMS定义的属性如下:

1:JMSXUserID:发送消息的用户标识,发送时提供商设置

2:JMSXAppID:发送消息的应用标识,发送时提供商设置

3:JMSXDeliveryCount:转发消息重试次数,第一次是1,第二次是2,… ,发送时提供商设置

4:JMSXGroupID:消息所在消息组的标识,由客户端设置

5:JMSXGroupSeq:组内消息的序号第一个消息是1,第二个是2,…,由客户端设置

6:JMSXProducerTXID :产生消息的事务的事务标识,发送时提供商设置

7:JMSXConsumerTXID :消费消息的事务的事务标识,接收时提供商设置

8:JMSXRcvTimestamp :JMS 转发消息到消费者的时间,接收时提供商设置

9:JMSXState:假定存在一个消息仓库,它存储了每个消息的单独拷贝,且这些消息从原始消息被发送时开始。每个拷贝的状态有:1(等待),2(准备),3(到期)或4(保留)。由于状态与生产者和消费者无关,所以它不是由它们来提供。它只和在仓库中查找消息相关,因此JMS没有提供这种API。由提供商设置

ActiveMQ的高级特性

ActiveMQ消息持久化

消息持久化是保证消息不丢失的重要方式。

ActiveMQ提供了一下三种的消息存储方式

  1. Memory消息存储-基于内存的消息存储;
  2. 基于日志消息存储方式,KahaDB是ActiveMQ的默认日志存储方式,它提供了容量和恢复能力;
  3. 基于JDBC的消息存储方式-数据存储于数据库中(例如:Mysql);

ActiveMQ持久化机制流程:默认是基于日志消息存储方式

 

ActiveMQ基于JDBC消息存储(springBoot)

a).修改application.yml文件:

spring:
  activemq:
    broker-url: tcp://192.168.1.129:61616
    user: admin
    password: admin
  jms:
    template:
      delivery-mode:

 

b).修改ActiveMQ/conf下的activemq.xml文件:

<bean name="mysql-ds" class="com.alibaba.druid.pool.DruidDataSource" destory-method="close">

    <property name="driverClassName" valuue="com.mysql.jdbc.Driver"/>

    <property name="url" value="jdbc:mysql://192.168.1.49:3306/test" />

    <property name="username" value="root"/>

    <property name="password" value="root"/>

</bean>

<persistenceAdapter>

    <jdbcPersistenceAdapter dataSource="#mysql-ds"/>

</persistenceAdapter>

 

c).拷贝mysql及druid数据源的jar包到activemq/lib目录下

d).重启activemq

 

消息事务

消息事务,是保证消息传递原子性的一个重要特征,和JDBC的事务特征类似;

一个事务性发送,其中一组(比如:5条消息为一组)消息要么能够全部保证到达服务器,要么都不到达服务器;

生产者、消费者与消息服务器直接都支持事务性;

ActiveMQ的事务主要偏向在生产者的应用;

ActiveMQ消息事务流程图:

实现方式(使用SpringBoot):使用原生JMS,使用spring的JmsTransactionManager

方式一:使用原生JMS

生产者:  

@Test
public void ptpProducerWithTx() {

    Session session = null;
    try {
        //获取连接工厂
        ConnectionFactory connectionFactory = jmsMessagingTemplate.getConnectionFactory();

        //创建连接
        Connection connection = connectionFactory.createConnection();
        //打开连接
        connection.start();
        /**
         * 参数一: 是否开启消息事务
         * 参数二:消息的确认机制
         * Session.AUTO_ACKNOWLEDGE 自动确认
         * Session.CLIENT_ACKNOWLEDGE
         * Session.DUPS_OK_ACKNOWLEDGE
         * Session.SESSION_TRANSACTED
         */
        session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //创建目的地
        Destination destination = session.createQueue(name);
        //创建生产者
        MessageProducer producer = session.createProducer(destination);
        for (int i = 1; i <= 10; i++) {
            //异常模拟
            //if (i == 4) {
            //    int a = 10 / 0;
            //}
            //创建消息
            TextMessage textMessage = session.createTextMessage("springboot-activemq-with-tx-message" + i);
            //发送消息
            producer.send(destination, textMessage);
        }
        //注意:一旦开启事务发送,那么就必修使用commint方法进行事务的提交,否则消息无法到达MQ服务器
        session.commit();
        System.out.println("消息发送成功");
    } catch (Exception e) {
        e.printStackTrace();
        //事务的回滚
        try {
            session.rollback();
        } catch (JMSException ex) {
            ex.printStackTrace();

        }
    }
}

 

消费者:

@JmsListener(destination = "${activemq.name}")
public void receiveMessage(Message message, Session session) {

    if (message instanceof TextMessage) {
        TextMessage objectMessage = (TextMessage) message;
        try {
            String object = objectMessage.getText();
            System.out.println(name + " 接收消息:" + object);
            //模拟异常
            // int a = 10/0;
            //提交事务
            session.commit();
        } catch (JMSException e) {
            e.printStackTrace();
            //消息回滚
            try {
                session.rollback();
            } catch (JMSException ex) {
                ex.printStackTrace();
            }
        }
    }
}

方式二:使用spring的JmsTransactionManager

生产者:

配置ActiveMQ的事务管理器

@Configuration
public class ActiveMQConfig {

    /**
     * 添加ActiveMQ事务管理器配置
     */
    @Bean
    public PlatformTransactionManager createPlatformTransactionManager(ConnectionFactory connectionFactory) {
        return new JmsTransactionManager(connectionFactory);
    }

}

 

模拟消息发送业务

@Service
public class MessageService {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Value("${activemq.name}")
    private String name;

    @Transactional //对消息发送加入事务管理(同事也对JDBC数据库的事务生效)
    public void sendMessage() {
        for (int i = 1; i <= 10; i++) {
            //异常模拟
            //if (i == 4) {
            //    int a = 10 / 0;
            //}
            jmsMessagingTemplate.convertAndSend(name, "springboot-activemq-no-tx-message" + i);
        }
    }

}

发送消息:

@Autowired
private MessageService messageService;

/**
 * 加入事务的消息发送--方案二:spring的JmsTransactionManager功能(JMS事务管理器)
 */
@Test
public void ptpProducerWithTx2() {
    messageService.sendMessage();
}

 

消费者:同方案一的消费者

 

消息确认机制

JMS消息只有在被确认之后,才认为已经被成功地消费了。消息的成功消费通常包含三个阶段:客户接受消息、客户处理消息和消息被确认。在事务性会话中,当一个事务被提交的时候,确认自动发生。在非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)。该参数有以下三个可选值(只在非事务下生效,事务下只有自动确认 ):

描述

Session.AUTO_ACKNOWLEDGE = 1

自动确认

当客户成功的从receive方法返回的时候,或者从MessageListener.onMessage方法成功返回的时候,会话自动确认客户收到的消息。

Session.CLIENT_ACKNOWLEDGE = 2

客户端手动确认

客户通过消息的Message.acknowledge方法确认消息。需要注意的是,在这种模式中,确认实在会话层上进行:确认一个被消费的消息将自动确认所有已被会话消费的消息。例如,如果一个消息消费者消费了10个消息,然后确认第5个消息,那么所有10个消息都被确认。

Session.DUPS_OK_ACKNOWLEDGE = 3

自动批量确认/延迟确认

该选择只是会话迟钝确认消息的提交。如果JMS provider失败,那么可能会导致一些重复的消息。如果是重复的消息,那么JMS provider必修把消息头的JMSRedelivered字段设置为true。

Session.SESSION_TRANSACTED=0

(事务提交并确认)

 

INDIVIDUAL_ACKNOWLEDGE = 4

(单条消息确认)

AcitveMQ补充了一个自定义的ACK_MODE,只有ActiveMQ支持,当然开发者也可以使用它

注意:消息确认机制与事务机制是冲突的,只能选其中一种,所以演示消息确认前,先关闭事务

Client端指定了ACK_MODE,但是在Clientbroker在交换ACK指令的时候,还需要告知ACK_TYPE,ACK_TYPE表示此确认指令的类型,不同的ACK_TYPE将传递着消息的状态,broker可以根据不同的ACK_TYPE对消息进行不同的操作。

ActiveMQ中定义了如下几种ACK_TYPE(参看MessageAck类):

DELIVERED_ACK_TYPE = 0    消息"已接收",但尚未处理结束;

STANDARD_ACK_TYPE = 2    "标准"类型,通常表示为消息"处理成功",broker端可以删除消息了;

POSION_ACK_TYPE = 1    消息"错误",通常表示"抛弃"此消息,比如消息重发多次后,都无法正确处理时,消息将会被删除或者DLQ(死信队列);

REDELIVERED_ACK_TYPE = 3    消息需"重发",比如consumer处理消息时抛出了异常,broker稍后会重新发送此消息;

INDIVIDUAL_ACK_TYPE = 4    表示只确认"单条消息",无论在任何ACK_MODE下;

UNMATCHED_ACK_TYPE = 5    BROKER间转发消息时,接收端"拒绝"消息;

消息投递方式

消息投递方式包含三种方式:异步投递、延迟投递、定时投递

1、异步投递 VS 同步投递

同步发送:消息生产者使用持久(Persistent)传递模式发送消息的时候,Producer.send()方法会被阻塞,直到broker发送一个确认消息给生产者(ProducerAck),这个确认消息按时broker已经成功接收到消息并把消息保存到二级存储中。

异步发送:如果应用程序能够容忍一些消息的丢失,那么可以使用异步发送。异步发送不会在收到broker的确认之前一直阻塞Producer.send()方法。

想要使用异步,在brokerUrl中增加jms.alwaysSyncSend=false&jms.userAsyncSend=true属性

a).如果设置了alwaysSyncSend=true(同步)系统会忽略userAsyncSend设置的值都采用同步;

b).当alwaysSyncSend=false(异步)时,“NON_PERSISTENT”(非持久化)、事务中的消息将使用“异步发送”;

c).当alwaysSyncSend=false(异步)时,如果指定userAsyncSend=true(异步),“PERSISTENT”(持久化)类型的消息使用异步发送。如果userAsyncSend=false(同步),“PERSISTENT”(持久化)类型的消息使用同步发送;

总结:默认情况(alwaysSyncSend=falseuserAsyncSend=false),非持久化消息、事务内的消息均采用异步发送;对于持久化消息采用同步发送!

没有使用事务并且正在发送持久性消息,则每个发送都是同步并阻塞

配置异步投递方式

方式一(原生JMS):

a).在连接上配置

new ActiveMQConnectionFactory("tcp://192.168.1.128:61616?jms.userAsyncSend=true");

b).通过ConnectionFactory

((ActiveMQConnectionFactory)connectionFactory).setUseAsyncSend(true);

c).通过connection

((ActiveMQConnection) connection).setUseAsyncSend(true);

注意:如果是Spring或者SpringBoot项目,通过修改JmsTemplate的默认参数实现异步或同步投递

 

方式二:SpringBoot的配置

/**
 * 配置用于异步发送的非持久化JmsTemplate
 */
@Autowired
@Bean
public JmsTemplate asyncJmsTemplate(ConnectionFactory connectionFactory){
    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    jmsTemplate.setExplicitQosEnabled(true);//deliveryMode, priority, timeToLive 的开关
    jmsTemplate.setDeliveryMode(DeliveryMode.NON_PERSISTENT);//非持久化
    return jmsTemplate;
}
/**
 * 配置用于同步发送的非持久化JmsTemplate
 * 默认为同步
 */
@Autowired
@Bean
public JmsTemplate syncJmsTemplate(ConnectionFactory connectionFactory){
    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    return jmsTemplate;
}

 

异步投递如何确认发送成功?

异步发送丢失场景:生产者设置了useAsyncSend=true(异步),使用producer.send(message)持续发送消息。由于消息不阻塞,生产者会认为所有send的消息都被成功发送到MQ了。如果此时MQ突然宕机,生产者端内存中尚未发送至MQ的消息就会丢失。

这时,可以给异步投递方法接收回调函数,已确认消息是否发送成功。

@Value("${activemq.name}")
private String name;
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
 * 测试异步/同步投递
 */
@Test
public void asyncAndSyncSend() {
    Session session = null;
    try {
        //获取连接工厂
        ActiveMQConnectionFactory connectionFactory = (ActiveMQConnectionFactory) jmsMessagingTemplate.getConnectionFactory();
        // 设置消息发送模式是AsyncSend模式,异步模式
        connectionFactory.setUseAsyncSend(true);
        //创建连接
        Connection connection = connectionFactory.createConnection();
        //打开连接
        connection.start();
        /**
         * 参数一: 是否开启消息事务
         * 参数二:消息的确认机制
         * Session.AUTO_ACKNOWLEDGE 自动确认
         * Session.CLIENT_ACKNOWLEDGE
         * Session.DUPS_OK_ACKNOWLEDGE
         * Session.SESSION_TRANSACTED
         */
        session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //创建目的地
        Destination destination = session.createQueue(name);
        //创建生产者
        ActiveMQMessageProducer producer = (ActiveMQMessageProducer) session.createProducer(destination);
        //非持久化
        producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

        for (int i = 1; i <= 10; i++) {
            //创建消息
            TextMessage textMessage = session.createTextMessage("springboot-activemq-with-async-message" + i);
            //设置消息唯一的ID
            String msgId = UUID.randomUUID().toString().replaceAll("-", "");
            //这一步没有效果,因为即使开发者设置值,但是消息提供者会自己生成一个id,该id使用来记录本地发送失败的数据的
            textMessage.setJMSMessageID(msgId);
            //发送消息
            producer.send(textMessage, new AsyncCallback() {
                @Override
                public void onSuccess() {
                    //使用msgId标识来进行消息发送成功的处理
                    System.out.println("msgId:" + msgId);
                }
                @Override
                public void onException(JMSException exception) {
                    //使用msgId标识来进行消息发送失败的处理
                    System.out.println("msgId:" + msgId + "发送失败");
                    exception.printStackTrace();
                }
            });
        }
        //注意:一旦开启事务发送,那么就必修使用commint方法进行事务的提交,否则消息无法到达MQ服务器
        session.commit();
        System.out.println("消息发送成功");
    } catch (Exception e) {
        e.printStackTrace();
        //事务的回滚
        try {
            session.rollback();
        } catch (JMSException ex) {
            ex.printStackTrace();
        }
    }
}

 

2、延迟投递

生产者提供两个发送消息的方法,一个是即使发送消息,一个是延时发送消息。

步骤如下:

1、修改activemq.xml

注意:修改activemq/conf目录下的activemq.xml文件中的broker标签,添加schedulerSupport=”true”配置 开启延时投递,重启activemq;

<broker xmlns="http://activemq.apache.org/schema/core"  
        brokerName="localhost" dataDirectory="${activemq.data}"
        schedulerSupport="true" />

2、在代码中设置延时时长

@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
 * 延时投递
 */
@Test
public void lazySendMessage() {
    Session session = null;
    try {
        //获取连接工厂
        ConnectionFactory connectionFactory = jmsMessagingTemplate.getConnectionFactory();
        //创建连接
        Connection connection = connectionFactory.createConnection();
        //打开连接
        connection.start();
        /**
         * 参数一: 是否开启消息事务
         * 参数二:消息的确认机制
         * Session.AUTO_ACKNOWLEDGE 自动确认
         * Session.CLIENT_ACKNOWLEDGE
         * Session.DUPS_OK_ACKNOWLEDGE
         * Session.SESSION_TRANSACTED
         */
        session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //创建目的地
        Destination destination = session.createQueue(name);
        //创建生产者
        ActiveMQMessageProducer producer = (ActiveMQMessageProducer) session.createProducer(destination);
        producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);//非持久化
        //创建消息
        TextMessage textMessage = session.createTextMessage("springboot-activemq-with-tx-message");
        //设置延时时长(延时10s)
        textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, 10000);
        //发送消息
        producer.send(destination, textMessage);
        //注意:一旦开启事务发送,那么就必修使用commint方法进行事务的提交,否则消息无法到达MQ服务器
        session.commit();
        System.out.println("消息发送成功");
    } catch (Exception e) {
        e.printStackTrace();
        //事务的回滚
        try {
            session.rollback();
        } catch (JMSException ex) {
            ex.printStackTrace();
        }
    }
}

ScheduledMessage投递的四个属性

属性

描述

ScheduledMessage.AMQ_SCHEDULED_DELAY

延迟投递的时间

ScheduledMessage.AMQ_SCHEDULED_PERIOD

重复投递的时间间隔

ScheduledMessage.AMQ_SCHEDULED_REPEAT

重复投递次数

ScheduledMessage.AMQ_SCHEDULED_CRON

Cron表达式

定时投递

步骤如下:

1、启动类添加定时注解

@SpringBootApplication
@EnableScheduling //开启定时任务
public class ProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class, args);
    }
}

2、在生产者添加@Scheduled设置定时

/**
 * 定时任务消息发送
 */
@Component
public class Producer {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Value("${activemq.name}")
    private String name;

    /**
     * 定时发送消息
     */
    @Scheduled(fixedDelay = 3000)
    public void sendMessage() {
        jmsMessagingTemplate.convertAndSend(name, "springboot-activemq-scheduled");
    }

}

死信队列

DLQ-死信队列(Dead Letter Queue)用来保存处理失败或者过期的消息。

出现以下情况时,消息会被redelivered(重新交付):

A transacted session is used and rollback() is called.

A transacted session is closed before commit() is called.

A session is using CLIENT_ACKNOWLEDGE and Session.recover() is called.

A client connection times out (perhaps the code being executed takes longer than the configured time-out period).

1.事务会话中,当还未进行session.commit()时,进行session.rollback(),那么所有还没commit的消息都会进行重发。

2.所有未ack的消息,在没有commint()之前进行session.closed()关闭事务,那么所有还没ack的消息broker端都会进行重发,而且是马上重发。

3.使用客户端手动确认的方式时(非事务会话),还未进行确认并且执行Session.recover(),那么所有还没acknowledge的消息都会进行重发。

4.消息被消费者拉取之后,超时没有响应ack,消息会被broker重发。

当一个消息被redelivered超过maximumRedeliveries(缺省为6次)次数时,会给broker发送一个" poison_ack",这个消息被认为是a poison pill,这时broker会将这个消息发送到DLQ,以便后续处理。

注意两点:

1、缺省持久消息过期,会被送到DLQ,非持久消息不会送到DLQ

2、缺省的死信队列是ActiveMQ.DLQ,如果没有特别指定,死信都会被发送到这个队列。

可以通过配置文件(activemq.xml)来调整死信发送策略,方案如下:

官网链接:http://activemq.apache.org/message-redelivery-and-dlq-handling

  1. 方案一)为每个队列创建死信队列
修改activemq/conf目录下的activemq.xml文件:
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">
<destinationPolicy>
    <policyMap>
        <policyEntries>

            <policyEntry queue=">" >
                <!--为每个队列建立死信队列 -->
                <deadLetterStrategy>
                    <individualDeadLetterStrategy queuePrefix="DLQ." useQueueForQueueMessages="true"/>
                </deadLetterStrategy>
            </policyEntry>

            <policyEntry topic=">" >
                <!--死信队列的默认配置 -->
                <pendingMessageLimitStrategy>
                    <constantPendingMessageLimitStrategy limit="1000"/>
                </pendingMessageLimitStrategy>

            </policyEntry>
        </policyEntries>
    </policyMap>
</destinationPolicy>

 

2、(方案二)RedeliveryPolicy重发策略设置

在消费者方配置如下:

 
@Configuration
public class ActiveMQConfig {
    /**
     * RedeliveryPolicy
     */
    @Bean
    public RedeliveryPolicy redeliveryPolicy() {
        RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
        //是否在每次尝试重新发送失败后,增长这个等待时间
        redeliveryPolicy.setUseExponentialBackOff(true);
        //重发次数,默认为6次,这里设置为10次
        redeliveryPolicy.setMaximumRedeliveries(10);
        //重发时间间隔,默认为1s
        redeliveryPolicy.setInitialRedeliveryDelay(2000);
        //第一次失败后重新发送之前等待500毫秒,第二次失败再等待500*2毫秒,第三次500*2*2,这里的2就是value
        redeliveryPolicy.setBackOffMultiplier(2);
        //是否避免消息碰撞
        redeliveryPolicy.setUseCollisionAvoidance(false);
        //设置重发最大拖延时间,-1表示没有拖延只有UseExponentialBackOff(true)为true时生效
        //当重连时间间隔大的最大重连时间间隔时,以后每次重连时间间隔都为最大重连时间间隔。
        redeliveryPolicy.setMaximumRedeliveryDelay(-1);
        return redeliveryPolicy;
    }

    @Bean
    public ActiveMQConnectionFactory activeMQConnectionFactory(@Value("${spring.activemq.broker-url}") String url, RedeliveryPolicy redeliveryPolicy) {
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory("admin", "admin", url);
        activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);
        return activeMQConnectionFactory;
    }

    /**
     * 添加ActiveMQ事务管理器配置
     */
    @Bean
    public PlatformTransactionManager createPlatformTransactionManager(ConnectionFactory connectionFactory) {
        return new JmsTransactionManager(connectionFactory);
    }

    /**
     * 消费者 消息确认机制配置
     *
     * @param connectionFactory
     * @return
     */
    @Bean("jmsListenerContainerFactory")
    public DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory(ConnectionFactory connectionFactory, PlatformTransactionManager platformTransactionManager) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);//连接工厂
        factory.setTransactionManager(platformTransactionManager);//事务管理器
        //是否关闭事务,开启事务
        factory.setSessionTransacted(true);
        //修改消息确认机制
        //自动确认
        factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
        return factory;
    }

}

 

@Component
public class MyListener {
    @Value("${activemq.name}")
    private String name;
    private int count = 0;
    /**
     * 消费方事务的配置,使用session形参控制
     *
     * @param message
     * @param session
     */
    @JmsListener(destination = "${activemq.name}",containerFactory = "jmsListenerContainerFactory")
    public void receiveMessage(Message message, Session session) {

        if (message instanceof TextMessage) {
            TextMessage objectMessage = (TextMessage) message;
            try {
                String object = objectMessage.getText();

                System.out.println("重复提交次数count:" + (++count));

                System.out.println(name + " 接收消息:" + object);
                //模拟异常
                int a = 10 / 0;
                //提交事务
                session.commit();
            } catch (JMSException e) {
                e.printStackTrace();
                //消息回滚
                try {
                    session.rollback();
                } catch (JMSException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

 

其他方案:https://www.cnblogs.com/rainwang/p/5146223.html

包括:

非持久消息保存到死信队列

<deadLetterStrategy>
    <sharedDeadLetterStrategy  processNonPersistent="true"/>
</deadLetterStrategy>

 

 

过期消息不保存到死信队列

<deadLetterStrategy>
    <sharedDeadLetterStrategy processExpired="false" />
</deadLetterStrategy>

持久消息不保存到死信队列

ActiveMQ企业经典面试问题

ActiveMQ宕机了怎么办?

  1. ActiveMQ主从集群方案:Zookeeper集群+Replicate LevelDB(kahadb)+ActiveMQ集群

官网链接:http://activemq.apache.org/replicated-leveldb-store

LevelDB存储已被弃用,不再受支持或建议使用。推荐的storeKahaDB

 

如何防止消费者消费重复消息?

如果因为网络延迟等原因,MQ无法及时接收到消息方的应答,导致MQ重试。在重试过程中造成重复消费的问题。

解决思路:

  1. 如果消费方是做数据库操作,那么可以报消息的ID作为表的唯一主键或者唯一约束,这样在重试的情况下,会触发冲突,从而避免数据出现脏数据。
  2. 如果消费方不是数据库操作,那么可以借助第三方的应用,例如redis,来记录消费记录。每次消费被消费完成的时候,把当前消息的ID作为key存入redis,每次存入redis,每次消费前,先到redis查询有没有该消息的消费者记录。

如何防止消息丢失?

以下手段可以防止消息丢失:

  1. 在消息生产者和消费者使用事务;
  2. 在消费者采用手动消息确认(ACK)
  3. 消息持久化,例如JDBC或kahadb

什么是死信队列?

DLQ-死信队列(Dead Letter Queue)用来保存处理失败或者过期的消息。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些ActiveMQ常见的面试题: 1. 什么是ActiveMQ?它的作用是什么? 答:ActiveMQ是一个开源的消息中间件,它的作用是实现异步通信和消息传递,可以用于解耦、异步处理、流量削峰等场景。 2. ActiveMQ的消息模型有哪些?它们的区别是什么? 答:ActiveMQ的消息模型包括点对点(P2P)模型和发布/订阅(Pub/Sub)模型,P2P模型中每个消息只有一个消费者可以消费,而Pub/Sub模型中每个消息可以被多个消费者消费。 3. ActiveMQ的消息传递方式有哪些?它们的区别是什么? 答:ActiveMQ的消息传递方式包括同步传递和异步传递,同步传递是指生产者发送消息后需要等待消费者返回确认消息之后才能继续发送下一个消息,而异步传递则是生产者发送消息后不需要等待消费者的确认消息,可以立即发送下一个消息。 4. ActiveMQ的消息持久化方式有哪些?它们的区别是什么? 答:ActiveMQ的消息持久化方式包括文件(File)持久化和数据库(Database)持久化,文件持久化是将消息保存在本地的文件系统中,而数据库持久化则是将消息保存在数据库中。 5. ActiveMQ的消息监听方式有哪些?它们的区别是什么? 答:ActiveMQ的消息监听方式包括消息驱动(Message-Driven)和轮询(Polling)两种,消息驱动是指在消息到达时立即通知消费者进行处理,而轮询则是在一定时间间隔内不断地检查是否有消息到达。 6. ActiveMQ和RabbitMQ的区别和联系是什么? 答:ActiveMQ和RabbitMQ都是开源的消息中间件,用于解耦、异步处理、流量削峰等场景,但是ActiveMQ相对于RabbitMQ更加灵活和易于使用,支持更多的协议和消息模型,而RabbitMQ则更加成熟和稳定,支持更多的消息传递方式和持久化方式。两者可以根据实际业务需求进行选择和使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值