AMQ的持久化問題

前言

​ 前面一篇AMQ專題中,我們發現對於Topic這種類型的消息,即使將deliveryMode設置爲持久化,只要生產者在消費者之前啓動。消息生產者發佈的消息還是會丟失。這是符合JMS規範的。

當然,作爲一個如此活躍的開源消息中間件,在實現JMS基本規範之後,必然會通過擴展的方式來實現Topic的持久化訂閱。

而所謂的deliveryMode持久化和訂閱持久化還是兩個不同的概念。本篇博客我們就通過實例來一探究竟。

DeliveryMode持久化

​ 在前面一篇中,我們通過producer.setDeliveryMode(DeliveryMode.PERSISTENT);將消息傳遞特性置爲持久化,當時嘗試過當消息類型是topic的時候,不管該值設置爲啥,只要先啓動Producer,那麼對於後啓動的Consumer都無法獲取原來發布的主題。

​ 那麼這個DeliveryMode究竟是用來幹啥的呢?

DeliveryMode中的是否持久化,指的是當重啓activeMQ之後,原來隊列或者主題中未被消費的消息是否仍然保留
​ 我這裏自己通過代碼進行了如下測試,測試步驟和結果如下:

創建producer,並將producer的deliveryMode設置成持久化,運行producer
在消息被consumer消費之前,重啓activeMQ
運行consumer,發現接收到了activeMQ重啓之前Producer發送的消息
修改producer,將producer的deliveryMode設置成非持久化,運行producer
在消息被consumer消費之前,重啓activeMQ
運行consumer,沒有接收到任何消息,原producer產生的消息丟失
​ 持久化和非持久化最終隊列控制檯分別如下:

AMQ的持久化問題

圖片描述(最多50字)

AMQ的持久化問題

圖片描述(最多50字)

 至此,不難發現,deliveryMode的是否持久化是針對activeMQ服務器是否重啓而言的。對於不支持持久化的設置,當mq重啓之後,沒有被消費的消息就會丟失。而支持持久化的設置,只要消息沒有被消費,重啓mq,仍然能被新加入的consumer消費。

訂閱持久化

​ JMS的規範是沒有要求實現訂閱持久化的。所幸的是activeMQ實現了這個特性。個人認爲所謂的訂閱持久化相對於消息的持久化,不過是一種僞持久化。先不做太多說明,我們直接看一個示例代碼:

生產者

public class SimpleProducer {
public static void main(String[] args) {
// STEP1: 得到連接工廠
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL);

    Connection connection = null;
    Session session = null;
    MessageProducer topicProducer = null;
    Destination topicDestination = null;
    try {
        // STEP2: 從連接工廠得到連接並且啓動連接
        connection = connectionFactory.createConnection();
        connection.start();

        // STEP3: 獲取會話
        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // STEP4: 創建主題 
        topicDestination = session.createTopic("KiDe-topic-Demo");

        // STEP5: 創建消息生產者
        topicProducer = session.createProducer(topicDestination);
        topicProducer.setDeliveryMode(DeliveryMode.PERSISTENT);     // 設置爲持久化

        // STEP6: 發送消息
        for (int i=0; i<20; i++) {
            TextMessage message = session.createTextMessage("Producer message:" + i);
            topicProducer.send(message);
        }

        // STEP7: 如果開啓了事務 ,此時需要調用session提交操作
        // session.commit();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (connection != null) {
            try {
                connection.close();
            } catch (JMSException e) {
            }
        }
    }
}

}
消費者

public class SimpleConsumer {
public static void main(String[] args) {
// STEP1: 創建連接工廠
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER,
ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_BROKER_URL);
Connection connection = null;
Session session = null;
MessageConsumer topicConsumer = null;
try {
// STEP2: 從連接工廠得到連接並且啓動連接
connection = connectionFactory.createConnection();
connection.setClientID("1"); // 如果要進行持久化訂閱,必須對連接設置clientID
connection.start();
// STEP3: 獲取會話
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // STEP4: 創建持久化訂閱者
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(session.createTopic("KiDe-topic-Demo"), "1");

        // STEP5: 設置消息接收監聽
        topicSubscriber.setMessageListener(new MessageListener() {

            @Override
            public void onMessage(Message paramMessage) {
                TextMessage message = (TextMessage) paramMessage;
                try {
                    System.out.println("消費者接收到主題消息:" + message.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
        TimeUnit.SECONDS.sleep(200);    // 睡眠200秒,使得客戶端可以接收到對應消息
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (connection != null) {
            try {
                connection.close();
            } catch (JMSException e) {
            }
        }
    }
}

}
​ 最終我的驗證步驟和結果如下:

運行producer,向activeMQ發送主題消息
運行consumer,發現未收到任何消息
運行producer,此時運行中的consumer接收到了topic消息
停止運行consumer,重新運行producer
重新運行consumer,此時consumer接收到了剛剛producer產生的消息
創建consumer的session的時候,同時創建兩個同clientId的session時會報同一通道已被佔用的錯誤
​ 分析以上步驟,我最終對這種僞持久化訂閱的總結如下:

要實現僞持久化訂閱,必須先向activeMQ發佈持久化訂閱消息,通過clientId來標識不同的訂閱渠道。
如果在發佈持久化訂閱消息之前producer就向mq發送了topic消息,那麼consumer還是沒法接收
activeMQ確定是否是同一持久化訂閱者的依據條件有兩個:connection.setClientID("3")中的clientId
以及session.createDurableSubscriber(session.createTopic("KiDe-topic-Demo"), "12")中的name
總結

deliveryMode的持久化和訂閱持久化是兩個不同的概念,二者互不干擾,組合實現業務需求
需要弄清參數的實際意義第一步自己動手寫實例,看運行結果是否與自己預期一致。第二步則是情況允許的時候,多看源碼,掌握好的代碼和設計

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