一,異步消息
像RMI和Hession/Burlap這些遠程調用機制都是同步的,當客戶端調用遠程方法時,客戶端必須等到遠程方法完成後,纔可以繼續執行,但是有些操作是不需要等待,這時就可以用異步消息。
有兩個主要概念,消息代理(message broker)和目的地(destination),當一個應用消息發送時,會將消息發送給消息代理,消息代理可以確保消息被投遞到目的地,同時解放發送者,使其可以繼續進行其他的業務。
不同的消息系統會有不同的消息路由模式,但是有兩種通用的目的地,隊列(queue)和主題(topic),每種類型與特定的消息模型關聯,分別是點對點模型和發佈/訂閱模型。
點對點模型:每條消息只會發送給一個接收者,消息投遞後就會從隊列中刪除,所以可以確保每條消息只有一個接收者,但是並不意味着一個隊列只可以有一個接收者,一個隊列可以有多個接收者。
發佈/訂閱模型:主題的所有訂閱者都可以接收到消息的副本。
二,異步消息相比同步rpc的優點
1,無需等待
2,面向消息和解耦,與面向方法調用rpc通信不同,發送異步消息是以數據爲中心的,客戶端沒有與特定的方法簽名綁定
3,與遠程服務位置解耦(位置獨立),同步rpc服務一般需要網絡地址定位,在使用消息後,客戶端只需要知道通過那個隊列或主題來發送消息,服務只需要能夠從隊列或主題中獲取消息即可。基於此(位置獨立),在點對點模型中,當服務過載時,可以添加服務實例來監聽相同的隊列就可以增加處理能力;在發佈/訂閱模型中,多個服務可以訂閱同一個主題,但是每個服務的處理邏輯可以不同。例如,一組服務共同監聽新員工消息這個主題,其中一個服務可以在工資系統中增加該員工,一個系統可以將新員工添加到HR門戶中,還有一個服務可以爲新員工分配可訪問系統權限。
4,確保投遞,即使消息發送時服務無法使用,消息也會存儲起來,直到服務可用爲止。
三,Java消息服務JMS(Java Message Service)
JMS是Java EE的標準/規範之一,這種規範指出:消息發送應該是一步的,非阻塞的,發送者和接收者可以說是互不影響。jms只是java ee中定義的一組標準的API,它自身並不是一個消息服務系統,它是消息傳送服務的一個抽象,也就是它定義了消息傳送的接口但是並沒有具體的實現,也就是說jms只是一組接口而已。
而ActiveMQ就是JMS規範的具體實現,它是apache下的一個項目,採用java開發;就像JDBC抽象了關係數據庫的訪問,JPA抽象了對象與關係數據庫的映射,JMS的具體實現由不同的消息中間件廠商提供,而Apache ActiveMQ就是一個,所以ActiveMQ是消息服務系統,而JMS不是。
四,以下是一個使用ActiveMQ的隊列消息demo
消息發送端:
package com.uiao.activemq;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;
import javax.jms.*;
/**
* 點對點消息發送端
*
* Created by uiao on 2018/3/10.
*/
public class QueueProducer {
public static Logger logger = Logger.getLogger(QueueProducer.class);
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageProducer messageProducer;
// 在這可以根據用戶名和密碼以及url使用有參的構造函數創建鏈接工廠
connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
destination = session.createQueue("mq-queue");
messageProducer = session.createProducer(destination);
//DeliveryMode.PERSISTENT 當activemq關閉的時候,隊列數據將會被保存
//DeliveryMode.NON_PERSISTENT 當activemq關閉的時候,隊列裏面的數據將會被清空
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
sendMessage(session, messageProducer);
messageProducer.setTimeToLive(1000000);
session.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception {
for (int i = 0; i < 20; i++) {
Thread.sleep(100);
// 消息轉化器可以自定義或者用jmsMessagingTemplate
TextMessage textMessage = session.createTextMessage("第 " + i + " 條文本消息");
// 傳遞序列化對象,傳遞javaBean,TopicProducer就是用的這個
MqBean bean = new MqBean();
bean.setAge(24);
bean.setName("uiao");
ObjectMessage objectMessage = session.createObjectMessage(bean);
// 傳遞流,用來傳遞文件
StreamMessage streamMessage = session.createStreamMessage();
// 傳遞map
MapMessage mapMessage = session.createMapMessage();
// 傳遞文件
BytesMessage bytesMessage = session.createBytesMessage();
logger.info("發送消息:" + textMessage.getText());
messageProducer.send(textMessage);
}
}
}
Session的方法createSession(boolean var1, int var2) 第一個參數爲true是表示支持事務,後面的自動確認爲Session.SESSION_TRANSACTED
第一個參數爲false時,var2的是可以爲Session.AUTO_ACKNOWLEDGE,Session.CLIENT_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE其中一個。
消息接收端:
package com.uiao.activemq;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;
import javax.jms.*;
/**
* 點對點消息接收端
*
* Created by uiao on 2018/3/10.
*/
public class QueueConsumer {
private static Logger logger = Logger.getLogger(QueueConsumer.class);
public static void main(String[] args) {
ConnectionFactory connectionFactory;
Connection connection = null;
Session session;
Destination destination;
MessageConsumer messageConsumer;
connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); // 需要接收端確認
destination = session.createQueue("mq-queue");
messageConsumer = session.createConsumer(destination);
// 不使用監視器的情況
while (true) {
TextMessage textMessage = (TextMessage) messageConsumer.receive(1000); // 設置超時時間,超過不等待消息
if (textMessage != null) {
logger.info("收到的消息:" + textMessage.getText());
textMessage.acknowledge();
} else {
break;
}
}
// 使用監視器的情況
/*messageConsumer.setMessageListener(new MessageListener() {
// 實現了一個監聽器
@Override
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
//MqBean mqBean = (MqBean) ((ObjectMessage) message).getObject();
if (null != message) {
logger.info("收到的消息:" + textMessage.getText());
}
// 如果session設置爲Session.CLIENT_ACKNOWLEDGE,要加上這一步
message.acknowledge();
} catch (Exception e) {
e.printStackTrace();
}
}
});*/
} catch (Exception e) {
e.printStackTrace();
}
}
}
打開瀏覽器,進入管理頁面http://localhost:8161/admin/queues.jsp
密碼可以在apache-activemq-5.15.4\conf\jetty-realm.properties設置:
輸入密碼後進入管理頁面
執行完QueueProducer發送端後,可以看到該隊列有0個接受端,總共有20條消息,其中0條消息已經處理了,20條消息尚未處理。
然後我們開兩個QueueConsumer接收端,再一次執行發送端後查看後臺
發送端控制檯打印:
接收端1控制檯打印:
接收端2控制檯打印:
可以看到我們起的兩個接收端服務幾乎平均的把消息接收了
再看一下管理頁面:
接收端從0個變成了2個,消息總數和處理的消息也從20變爲40。
主題和隊列在使用上一致,只不過主題使用Topic實現Destination接口。
除此之外,針對冗長和複雜的JMS代碼,我們還可以使用Spring提供的JMS模板JmsTemplate來創建連接,獲得會話,以及發送和接受消息;JmsTemplate還可以處理所有的拋出的JMSException異常。
五,幾個常見問題
1,消息確認機制
消息只有被確認之後,才認爲是被成功消費,然後消息纔會從隊列或主題中刪除。
客戶端接收消息 = 》 消息處理 =》 消息確認
四種確認機制:
(1)、Session.AUTO_ACKNOWLEDGE;客戶(消費者)成功從receive方法返回時,或者從MessageListener.onMessage方法成功返回時,會話自動確認消息,然後自動刪除消息.
(2)、Session.CLIENT_ACKNOWLEDGE;客戶通過顯式調用消息的acknowledge方法確認消息,。 即在接收端調用message.acknowledge();方法,否則,消息是不會被刪除的.
(3)、Session. DUPS_OK_ACKNOWLEDGE ;不是必須確認,是一種“懶散的”消息確認,消息可能會重複發送,在第二次重新傳送消息時,消息頭的JMSRedelivered會被置爲true標識當前消息已經傳送過一次,客戶端需要進行消息的重複處理控制。
(4)、 Session.SESSION_TRANSACTED;事務提交併確認。
2,丟消息怎麼辦?
可以設置持久化消息,messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
使用acknowledge()方法,客戶端返回確認信息。
3,事務消息
創建回話Session使用transacted = true,事務消息在發送和接收後必須顯示的調用session.commit(),事務消息會自動確認。與設置的確認機制無關
3,消息接收方式
receive方法接收,一次即結束;使用監聽器接收,實現onMessage方法,作爲一個服務實時監測。
項目地址:https://gitee.com/uiaoadf/myActiveMQ
https://www.jianshu.com/p/b1a5acac15ef
《Spring 實戰》