消息隊列學習-ActiveMQ(二)
5 JMS規範和落地產品
5.1 是什麼
5.1.1 JavaEE
JavaEE 是一套使用Java進行企業級應用開發的一直遵守的13個核心規範工業標準。JavaEE平臺提供了一個基於組件的方法來加快設計、開發、裝配及部署企業應用程序
- JDBC(Java Database)數據庫連接
- JNDI(Java Naming and Directory Interfaces)Java 的命名和目錄接口
- EJB(Enterprise JavaBean)
- RMI(Remote Method Invoke)遠程方法調用
- Java IDL(Interface Description Language)/CORBA(Common Object Broker Architecture)接口定義語言/公用對象請求代理程序體系結構
- JSP(Java Server Page)
- Servlet
- XML(Extensible Markup Language)可擴展標記語言
- JMS(Java Message Service)Java消息服務
- JTA(Java Transaction API)Java事務API
- JTS(Java Transaction Service)Java事務服務
- JavaMail
- JAF(JavaBean Activation Framework)
5.1.2 JMS
Java消息服務指的是兩個應用程序之間進行異步通信的API,它爲標準消息協議和消息服務提供了一組通用接口,包括創建、發送、讀取消息等,用於支持JAVA應用程序開發。
5.2 MQ中間件的其他落地產品
消息隊列的詳細比較
特性 | ActiveMQ | RabbitMQ | Kafka | RocketMQ |
---|---|---|---|---|
PRODUCER-COMSUMER | 支持 | 支持 | 支持 | 支持 |
PUBLISH-SUBSCRIBE | 支持 | 支持 | 支持 | 支持 |
PRODUCER-COMSUMER | 支持 | 支持 | 支持 | 支持 |
REQUEST-REPLY | 支持 | 支持 | - | 支持 |
API完備性 | 高 | 高 | 高 | 低(靜態配置) |
多語言支持 | 支持,JAVA優先 | 語言無關 | 支持,JAVA優先 | 支持 |
單機吞吐量 | 萬級 | 萬級 | 十萬級 | 單機萬級 |
消息延遲 | - | 微秒級 | 毫秒級 | - |
可用性 | 高(主從) | 高(主從) | 非常高(分佈式) | 高 |
消息丟失 | - | 低 | 理論上不會丟失 | - |
消息重複 | - | 可控制 | 理論上會有重複 | - |
文檔的完備性 | 高 | 高 | 高 | 中 |
提供快速入門 | 有 | 有 | 有 | 無 |
首次部署難度 | - | 低 | 中 | 高 |
5.3 JMS的組成結構和特點
組成結構:
- JMS provider:實現JMS接口和規範的消息中間件,也就是MQ服務器
- JMS producer:消息生產者,創建和發送JMS消息的客戶端應用
- JMS consumer:消息消費者,接受和處理JMS消息的客戶端應用
- JMS message:消息(包括消息頭、消息屬性、消息體)
5.3.1 JSM message消息頭組成
- JMSDestination:消息發送的目的地,主要指Queue和Topic
- JMSDeliveryMode:持久模式/非持久模式
- JMSExpiration:設置過期時間,默認永不過期
- JMSPriority:優先級,0-4普通消息,5-9加急消息,默認是4級,只保證加急先於普通
- JMSMessageID:唯一識別每個消息的標識由MQ產生
5.3.2 消息體
- 封裝具體的消息數據
- 五種消息體格式
- TextMessage:普通字符串消息,包含一個string
- MapMessage:一個Map類型的消息,key爲string類型,而值爲Java的基本類型
- ByteMessage:二進制數組消息,包含一個byte[]
- StreamMessage:Java數據流消息,用標準流操作來順序的填充和讀取
- ObjectMessage:對象消息,包含一個可序列化的Java對象
- 發送和接受的消息體類型必須一致對應
5.3.3 消息屬性
是啥:以屬性名和屬性值對的形式制定,可以看做消息頭的擴展
場景:如果需要除消息字段以外的值,那麼可以使用消息屬性。
作用:識別/去重/重點標註等操作非常有用的方法
用法:textMessage.setStringProperty("c01", "vip");
5.4 JMS的可靠性
5.4.1 PERSISTENT:持久性
- 參數說明:
- 非持久
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
非持久化:當服務器宕機,消息不存在 - 持久
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
持久化:當服務器宕機,消息依然存在
- 非持久
- 持久化的Queue
隊列默認是持久化的,此模式保證這些消息只被傳送一次和成功使用一次。 - 持久的Topic
JmsConsumer_Topic_Persist.java
public class JmsConsumer_Topic_Persist {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private static final String TOPIC_NAME = "Topic-Persist";
public static void main(String[] args) throws JMSException, IOException {
System.out.println("****我是李四");
// 1. 創建連接工廠,按照給定的URL地址,採用默認用戶名和密碼
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
// 2. 通過連接工廠,獲得連接connection並啓動
Connection connection = activeMQConnectionFactory.createConnection();
connection.setClientID("李四");
// 3. 創建會話session
// 兩個參數,第一個叫事務/第二個叫簽收
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
// 4. 創建目的地(具體是隊列還是主題)
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber
= session.createDurableSubscriber(topic, "remarl");
connection.start();
Message message = topicSubscriber.receive();
while (null != message) {
TextMessage textMessage = (TextMessage) message;
System.out.println("****收到持久化topic"+textMessage.getText());
message = topicSubscriber.receive();
}
session.close();
connection.close();
}
}
JmsProduce_Topic_Persist.java
public class JmsProduce_Topic_Persist {
private static final String ActiveMQ_URL = "tcp://192.168.188.128:61616";
private static final String TOPIC_NAME = "Topic-Persist";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic(TOPIC_NAME);
MessageProducer messageProducer = session.createProducer(topic);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
connection.start();
for (int i = 1; i <= 3; i++) {
TextMessage textMessage
= session.createTextMessage("TOPIC_NAME--->" + i);
messageProducer.send(textMessage);
}
messageProducer.close();
session.close();
connection.close();
System.out.println("***********TOPIC_NAME消息發佈到MQ完成");
}
}
啓動訂閱者,並將訂閱者停止,此時其處於離線訂閱者:
啓動生產者,發送消息:
在啓動被停止的離線訂閱者,依然能獲取到消息:
5.4.2 事務
- 設置爲
false
:只要執行send,就進入到隊列中。關閉事務,那第2個簽收參數的設置需要有效。 - 設置爲
true
:先執行send在執行commit,消息才被真正的提交到隊列中。消息需要批量發送的,需要緩衝區處理。
public class JmsProduce_TX {
private static final String ActiveMQ_URL = "tcp://192.168.188.131:61616";
private static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException {
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageProducer messageProducer = session.createProducer(queue);
for (int i = 1; i <= 3; i++) {
TextMessage textMessage
= session.createTextMessage("tx msg--->" + i);
messageProducer.send(textMessage);
}
session.commit();
messageProducer.close();
session.close();
connection.close();
System.out.println("***********消息發佈到MQ完成");
}
}
注意:若未提交,則沒有發送數據數據
public class JmsConsumer_TX {
private static final String ActiveMQ_URL = "tcp://192.168.188.131:61616";
private static final String QUEUE_NAME = "queue01";
public static void main(String[] args) throws JMSException, IOException {
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(ActiveMQ_URL);
Connection connection = activeMQConnectionFactory.createConnection();
connection.start();
Session session = connection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(QUEUE_NAME);
MessageConsumer messageConsumer = session.createConsumer(queue);
while (true) {
TextMessage textMessage=(TextMessage)messageConsumer.receive(4000L);
if (textMessage != null) {
System.out.println("****消費者收到消息:msg---"
+ textMessage.getText());
}else {
break;
}
}
session.commit();
messageConsumer.close();
session.close();
connection.close();
}
}
注意:若未提交,則會出現多次消費數據
5.4.3 Acknowledge:簽收
- 非事務
- 自動簽收(默認):
Session.AUTO_ACKNOWLEDGE
- 手動簽收:
Session.CLIENT_ACKNOWLEDGE
,客戶端調用acknowledge
方法手動簽收 - 允許重複消息:
Session.DUPS_OK_ACKNOWLEDGE
- 自動簽收(默認):
- 事務
生產事務開啓,只有commit後才能將全部消息變成已消費 - 簽收和事務關係
- 在事務性會話中,當一個事務被成功提交則消息會被自動簽收。如果事務回滾,則消息會被再次傳送。
- 非事務性會話中,消息何時被確認取決於創建會話時的應答模式
5.5 JMS的點對點總結
點對點模型是基於隊列的,生產者發消息到隊列,消費者從隊列接收消息,隊列的存在使得消息的異步傳輸成爲可能。
1:如果在Session關閉時有部分消息被收到但還沒有被簽收(acknowledge),那當消費者下次連接到相同的隊列時,這些消息還會被再次接收
2:隊列可以長久的保存消息直到消費者收到消息。消費者不需要因爲擔心消息會丟失而時刻和隊列保持激活的鏈接狀態,充分體現了異步傳輸模式的優勢
5.6 JMS的發佈訂閱總結
JMS Pub/Sub 模型定義瞭如何向一個內容節點發布和訂閱消息,這些節點被稱作topic
主題可以被認爲是消息的傳輸中介,發佈者**(publisher)發佈消息到主題,訂閱者(subscribe)**從主題訂閱消息。
主題使得消息訂閱者和消息發佈者保持互相獨立不需要解除即可保證消息的傳送
- 非持久訂閱
非持久訂閱只有當客戶端處於激活狀態,也就是和MQ保持連接狀態才能收發到某個主題的消息。
如果消費者處於離線狀態,生產者發送的主題消息將會丟失作廢,消費者永遠不會收到。
一句話:先訂閱註冊才能接受到發佈,只給訂閱者發佈消息。 - 持久訂閱
客戶端首先向MQ註冊一個自己的身份ID識別號,當這個客戶端處於離線時,生產者會爲這個ID保存所有發送到主題的消息,當客戶再次連接到MQ的時候,會根據消費者的ID得到所有當自己處於離線時發送到主題的消息
當持久訂閱狀態下,不能恢復或重新派送一個未簽收的消息。
持久訂閱才能恢復或重新派送一個未簽收的消息。 - 用哪個?
當所有的消息必須被接收,則用持久訂閱。當消息丟失能夠被容忍,則用非持久訂閱