文章目錄
1 PTP處理模式(Queue)
1.1 ptp基本理解
- 消息生產者生產消息發送到
queue
中, 然後消息消費者從queue
中取出並且消費消息。 - 消息被消費以後,
queue
中不再有存儲, 所以消息消費者不可能消費到已經被消費的消息。 Queue
支持存在多個消費者, 但是對一個消息而言, 只會有一個消費者可以消費、 其它的則不能消費此消息了。- 當消費者不存在時, 消息會一直保存, 直到有消費消費
1.2 pom依賴
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-core -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.jms/jms -->
<dependency>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
<version>1.1</version>
</dependency>
1.3 ptp的消費者
public class ConsumerPTP {
public String consumer() {
//連接工廠
ConnectionFactory factory = null;
//連接
Connection connection = null;
//目的地
Destination destination = null;
//會話
Session session = null;
//消息發送者
MessageConsumer consumer = null;
//消息對象
Message message = null;
String resultCode = null;
try {
//創建連接工廠
//創建工廠三個參數:用戶名,密碼,連接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通過工廠創建連接對象
//創建連接的方法有重載,其中有createConnection(String username,String password)
//可以在創建連接時,只傳遞地址 ,不傳遞用戶信息
connection = factory.createConnection();
//建議啓動連接,消息的發送者不是必須啓動連接,但是消息的消費者必須啓動連接
connection.start();
//兩個參數:boolean transacted 是否支持事物, int acknowledgeMode如何確認消息
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//創建目的地。參數名稱是目的地是唯一標記
destination=session.createQueue("first-activemq");
//通過會話,創建消息的發送者producer
//創建的消息發送者,發送的消息一定到指定的目的地中
consumer = session.createConsumer(destination);
//獲取隊列中的消息。receive方法是一個主動獲取消息的方法。執行一次拉取一個消息
message = consumer.receive();
resultCode = ((TextMessage)message).getText();
System.out.println("========消息已經發送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(consumer != null) {//回收消息發送者
try {
consumer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收會話對象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收連接對象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
return resultCode;
}
public static void main(String[] args) {
ConsumerPTP ptp = new ConsumerPTP();
System.out.println("========="+ptp.consumer()+"=========");
}
}
1.4 ptp的提供者
public class ProviderPTP {
/**
* 把消息發送到activemq中,具體消息內容爲參數信息
* 開發jms相關代碼過程中,都在javax.jms包下的類型
* @param datas - 消息內容
*/
public void provider(String datas) {
//連接工廠
ConnectionFactory factory = null;
//連接
Connection connection = null;
//目的地
Destination destination = null;
//會話
Session session = null;
//消息發送者
MessageProducer producer = null;
//消息對象
Message message = null;
try {
//創建連接工廠
//創建工廠三個參數:用戶名,密碼,連接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通過工廠創建連接對象
//創建連接的方法有重載,其中有createConnection(String username,String password)
//可以在創建連接時,只傳遞地址 ,不傳遞用戶信息
connection = factory.createConnection();
//建議啓動連接,消息的發送者不是必須啓動連接,但是消息的消費者必須啓動連接
connection.start();
//連個參數:boolean transacted 是否支持事物, int acknowledgeMode如何確認消息
/**
* transacted:是否支持事物,
* true:支持事物,那麼第二個參數默認無效建議是Session.SESSION_TRANSACTED
* false:不支持事物,常用參數。那麼第二個參數必須傳,且有效
* acknowledgeMode:如何確認消息的處理
* AUTO_ACKNOWLEDGE--自動確認消息,消息的消費者處理消息後,自動確認,商業開發不推薦
* CLIENT_ACKNOWLEDGE--客戶端手動確認,消息的消費者處理後,必須手工確認
* DUPS_OK_ACKNOWLEDGE--有副本的客戶端手動確認,一個消息可以多次處理,可以降低session消耗,可以容忍重複消息時使用(不推薦)
*/
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//創建目的地。參數名稱是目的地是唯一標記
destination=session.createQueue("first-activemq");
//通過會話,創建消息的發送者producer
//創建的消息發送者,發送的消息一定到指定的目的地中
producer = session.createProducer(destination);
//創建文本消息對象,作爲具體數據內容的載體
message= session.createTextMessage(datas);
//使用producer,發送消息到activemq中的目的地,若失敗則拋出異常
producer.send(message);
System.out.println("========消息已經發送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(producer != null) {//回收消息發送者
try {
producer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收會話對象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收連接對象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ProviderPTP ptp = new ProviderPTP();
ptp.provider("測試activemq");
}
}
2 Publish/Subscribe 處理模式(Topic)
2.1 topic基本理解
- 消息生產者(發佈) 將消息發佈到
topic
中, 同時有多個消息消費者(訂閱) 消費該消息。 - 和點對點方式不同, 發佈到
topic
的消息會被所有訂閱者消費。 - 當生產者發佈消息, 不管是否有消費者。 都
不會保存
消息 - 一定要先有消息的消費者, 後有消息的生產者。
2.2 pom依賴
<!-- https://mvnrepository.com/artifact/org.apache.activemq/activemq-core -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.jms/jms -->
<dependency>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
<version>1.1</version>
</dependency>
2.3 topic的消費者
public class ConsumerTopic {
public String consumer() {
//連接工廠
ConnectionFactory factory = null;
//連接
Connection connection = null;
//目的地
Destination destination = null;
//會話
Session session = null;
//消息發送者
MessageConsumer consumer = null;
//消息對象
Message message = null;
String resultCode = null;
try {
//創建連接工廠
//創建工廠三個參數:用戶名,密碼,連接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通過工廠創建連接對象
//創建連接的方法有重載,其中有createConnection(String username,String password)
//可以在創建連接時,只傳遞地址 ,不傳遞用戶信息
connection = factory.createConnection();
//建議啓動連接,消息的發送者不是必須啓動連接,但是消息的消費者必須啓動連接
connection.start();
//兩個參數:boolean transacted 是否支持事物, int acknowledgeMode如何確認消息
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//創建目的地。參數名稱是目的地是唯一標記
destination=session.createTopic("test-topicmq");
//通過會話,創建消息的發送者producer
//創建的消息發送者,發送的消息一定到指定的目的地中
consumer = session.createConsumer(destination);
//獲取隊列中的消息。receive方法是一個主動獲取消息的方法。執行一次拉取一個消息
message = consumer.receive();
resultCode = ((TextMessage)message).getText();
System.out.println("========消息已經發送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(consumer != null) {//回收消息發送者
try {
consumer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收會話對象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收連接對象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
return resultCode;
}
public static void main(String[] args) {
ConsumerTopic ptp = new ConsumerTopic();
System.out.println("========="+ptp.consumer()+"=========");
}
}
2.4 topic的提供者
public class ProviderTopic {
public static void main(String[] args) {
ProviderTopic ptp = new ProviderTopic();
ptp.provider("測試topic activemq");
}
public void provider(String datas) {
// 連接工廠
ConnectionFactory factory = null;
// 連接
Connection connection = null;
// 目的地
Destination destination = null;
// 會話
Session session = null;
// 消息發送者
MessageProducer producer = null;
// 消息對象
Message message = null;
try {
//創建連接工廠
//創建工廠三個參數:用戶名,密碼,連接地址
factory = new ActiveMQConnectionFactory("admin","admin","tcp://192.168.126.133:61616");
//通過工廠創建連接對象
//創建連接的方法有重載,其中有createConnection(String username,String password)
//可以在創建連接時,只傳遞地址 ,不傳遞用戶信息
connection = factory.createConnection();
//建議啓動連接,消息的發送者不是必須啓動連接,但是消息的消費者必須啓動連接
connection.start();
//連個參數:boolean transacted 是否支持事物, int acknowledgeMode如何確認消息
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
//創建目的地。參數名稱是目的地是唯一標記
//destination=session.createQueue("first-activemq");
session.createTopic("test-topicmq");
//通過會話,創建消息的發送者producer
//創建的消息發送者,發送的消息一定到指定的目的地中
producer = session.createProducer(destination);
//創建文本消息對象,作爲具體數據內容的載體
message= session.createTextMessage(datas);
//使用producer,發送消息到activemq中的目的地,若失敗則拋出異常
producer.send(message);
System.out.println("========消息已經發送========");
}catch (Exception e) {
e.printStackTrace();
}finally {
if(producer != null) {//回收消息發送者
try {
producer.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(session != null) {//回收會話對象
try {
session.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
if(connection != null) {//回收連接對象
try {
connection.close();
}catch(JMSException e) {
e.printStackTrace();
}
}
}
}
}
3 PTP 和 PUB/SUB 簡單對比
Topic | Queue | |
---|---|---|
概要 | Publish Subscribe messaging 發佈訂閱消息 |
Point-to-Point 點對點 |
有無狀態 | topic 數據默認不落地, 是無狀態的 |
Queue 數據默認會在mq 服務器上以文件形式保存, 比如 Active MQ 一 般 保 存 在$AMQ_HOME\data\kahadb 下面。也可以配置成 DB 存儲 |
完整性保障 | 並不保證publisher 發佈的每條數據, Subscriber 都能接受到 |
Queue 保證每條數據能被 receiver 接收。 消息不超時 |
消息是否會丟失 | 一般來說 publisher 發佈消息到某一個topic 時, 只有正在監聽該 topic 地址的sub 能夠接收到消息;如果沒有 sub 在監聽, 該topic 就丟失了 |
Sender 發 送 消 息 到 目 標Queue , receiver 可以異步接收這個 Queue 上的消息。Queue 上的消息如果暫時沒有 receiver 來取, 也不會丟失。 前提是消息不超時 |
消息發佈接收策略 | 一對多的消息發佈接收策略, 監聽同一個 topic 地址的多個 sub 都能收到 publisher 發送的消息。Sub 接收完通知 mq 服務器 |
一對一的消息發佈接收策略, 一個 sender 發送的消息, 只能有一個receiver 接收。receiver 接收完後, 通知 mq 服務器已接收, mq 服務器對 queue 裏的消息採取刪除或其他操作 |
4 API
4.1 Producer API
4.1.1發送消息
MessageProducer
send(Message message)
;發送消息到默認目的地, 就是創建Producer
時指定的目的地。send(Destination destination, Message message)
; 發送消息到指定目的地,Producer
不建議綁定目的地。也就是創建Producer
的時候,不綁定目的地。這樣寫法:session.createProducer(null)
send(Message message, int deliveryMode, int priority, long timeToLive)
;發送消息到默認目的地, 且設置相關參數。
deliveryMode:
持久化方式(DeliveryMode.PERSISTENT|DeliveryMode.NON_PERSISTENT
)。
priority:
優先級。timeToLive:
消息有效期(單位毫秒)。send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive)
發送消息到指定目的地, 且設置相關參數。
4.1.2 消息有效期
消息過期後, 默認會將失效消息保存到死信隊列(ActiveMQ.DLQ)
。
不持久化的消息, 在超時後直接丟棄, 不會保存到死信隊列中。
死信隊列名稱可配置, 死信隊列中的消息不能恢復
。
死信隊列是在activemq.xml
中配置的。
4.1.3 消息優先級
可以在發送消息時, 指定消息的權重,broker
可以建議權重較高的消息將會優先發送給 Consumer
。 在某些場景下, 我們通常希望權重較高的消息優先傳送; 不過因爲各種原因, priority
並不能決定消息傳送的嚴格順序(order
)。
JMS
標準中約定 priority
可以爲0~9
的整數數值, 值越大表示權重越高,默認值爲 4
。
activeMQ
中各個存儲器對 priority
的支持並非完全一樣。 比如 JDBC
存儲器可以支持0~9
,因爲JDBC
存儲器可以基於 priority
對消息進行排序和索引化;但是對於 kahadb/levelDB
等這種基於日誌文件的存儲器而言, priority
支持相對較弱, 只能識別三種優先級(LOW: <4,NORMAL: =4,HIGH: > 4)
。
4.1.3.1 開啓
在 broker
端, 默認是不存儲 priority
信息的, 我們需要手動開啓, 修改 activemq.xml
配置文件, 在 broker
標籤的子標籤 policyEntries
中增加下述配置:
<policyEntry queue=">" prioritizedMessages="true"/>
不過對於“非持久化”類型的消息(如果沒有被 swap
到臨時文件), 它們被保存在內存中,它們不存在從文件Paged in
到內存的過程,因爲可以保證優先級較高的消息,總是在prefetch
的時候被優先獲取, 這也是“非持久化”消息可以擔保消息發送順序的優點。
Broker
在收到Producer
的消息之後, 將會把消息cache
到內存, 如果消息需要持久化,那麼同時也會把消息寫入文件; 如果通道中 Consumer
的消費速度足夠快(即積壓的消息很少, 尚未超過內存限制, 通過上文能夠知道, 每個通道都可以有一定的內存用來 cache
消息), 那麼消息幾乎不需要從存儲文件中Paged In
, 直接就能從內存的cache
中獲取即可,這種情況下, priority
可以擔保“全局順序”; 不過, 如果消費者滯後太多,cache
已滿, 就會觸發新接收的消息直接保存在磁盤中, 那麼此時,priority
就沒有那麼有效了。
在 Queue
中, prefetch
的消息列表默認將會採用輪詢
的方式(roundRobin
, 注意並不是roundRobinDispatch
)[備註:因爲 Queue
不支持任何 DispatchPolicy
],依次添加到每個 consumer
的 pending buffer
中, 比如有 m1-m2-m3-m4
四條消息, 有 C1-C2
兩個消費者, 那麼 :m1->C1,m2->C2,m3->C1,m4->C2
。 這種輪序方式, 會對基於權重的消息發送有些額外的影響, 假如四條消息的權重都不同, 但是(m1,m3)->C1
, 事實上m2
的權重>m3
,對於 C1
而言,它似乎丟失了“順序性”。
4.1.3.2 強順序
<policyEntry queue=">" strictOrderDispatch="true"/>
strictOrderDispatch
“嚴格順序轉發”, 這是區別於“輪詢”的一種消息轉發手段; 不過不要誤解它爲“全局嚴格順序”, 它只不過是將prefetch
的消息依次填滿每個consumer
的pending buffer
。 比 如 上 述 例 子 中 , 如 果C1-C2
兩 個 消 費 者 的buffer
尺 寸 爲 3 , 那 麼(m1,m2,m3)->C1,(m4)->C2
;當 C1
填充完畢之後, 纔會填充 C2
。由此這種策略可以保證 buffer
中所有的消息都是“權重臨近的”、 有序的。 (需要注意: strictOrderDispatch
並非是解決priority
消息順序的問題而生, 只是在使用 priority
時需要關注它)。
4.1.3.3 嚴格順序
<policyEntry queue=">" prioritizedMessages="true" useCache="false"
expireMessagesPeriod="0" queuePrefetch="1"/>
useCache=false
來關閉內存, 強制將所有的消息都立即寫入文件(索引化, 但是會降低消息的轉發效率);queuePrefetch=1
來約束每個consumer
任何時刻只有一個消息正在處理, 那些消息消費之後, 將會從文件中重新獲取, 這大大增加了消息文件操作的次數, 不過每次讀取肯定都是 priority
最高的消息
4.2 Consumer API
4.2.1 消息的確認
Consumer
拉取消息後, 如果沒有做確認 acknowledge
, 此消息不會從 MQ
中刪除。
如果消息拉去到consumer
後, 未確認, 那麼消息被鎖定。 如果 consumer
關閉的時候仍舊沒有確認消息, 則釋放消息鎖定信息。 消息將發送給其他的consumer
處理。
消息一旦處理, 應該必須確認。 類似數據庫中的事務管理機制。
例:consumer
確認方法(Session.CLIENT_ACKNOWLEDGE
客戶端手動確認時才需要如下,若是AUTO_ACKNOWLEDGE
這不需要了)
message.acknowledge()
4.2.2 消息的過濾
對消息消費者處理的消息數據進行過濾。 這種處理可以明確消費者的角色, 細分消費者的功能。
設置過濾:
Session.createConsumer(Destination destination, String messageSelector);
過濾信息爲字符串, 語法類似 SQL92
中的 where
子句條件信息。 可以使用諸如AND、OR、 IN、 NOT IN
等關鍵字。 詳細內容可以查看javax.jms.Message
的幫助文檔。
注意:
消息的生產者在發送消息的的時候, 必須設置可過濾的屬性信息
, 所有的屬性信息設置方法格式爲:setXxxxProperty(String name, T value)
。 其中方法名中的Xxxx
是類型,如 setObjectProperty/setStringProperty
等。