前言
前面一篇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產生的消息丟失
持久化和非持久化最終隊列控制檯分別如下:
圖片描述(最多50字)
圖片描述(最多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的持久化和訂閱持久化是兩個不同的概念,二者互不干擾,組合實現業務需求
需要弄清參數的實際意義第一步自己動手寫實例,看運行結果是否與自己預期一致。第二步則是情況允許的時候,多看源碼,掌握好的代碼和設計