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)用來保存處理失敗或者過期的消息。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章