每個 Apache ActiveMQ Artemis 支援的訊息協定和 API 都定義了一組不同的訊息資源。
-
JMS 使用佇列和主題
-
STOMP 使用通用的目的地
-
MQTT 使用主題
-
AMQP 使用通用的節點
為了處理每個這些不同的語意和使用案例,訊息代理程式具有彈性且強大的位址模型,該模型基於以下核心資源集
-
位址
-
佇列
-
路由類型
1. 位址
訊息會傳送至位址。位址會被賦予一個唯一的名稱、一個路由類型,以及零個或多個佇列。
2. 佇列
訊息會從佇列中被消費。佇列會繫結至一個位址。它會被賦予一個唯一的名稱和一個路由類型。可以有零個或多個佇列繫結至一個位址。當訊息傳送至位址時,會根據設定的路由類型將其路由至一個或多個佇列。
佇列的名稱必須是全域唯一的。例如,您不能在位址 a1
上有名為 q1
的佇列,同時也在位址 a2
上有名為 q1
的佇列。
3. 路由類型
路由類型決定如何將訊息從位址路由到繫結至該位址的佇列。支援兩種不同的路由類型:anycast 和 multicast。
如果您希望將訊息路由到… | 請使用此路由類型… |
---|---|
位址上的單一佇列 |
anycast |
位址上的每個佇列 |
multicast |
可以為同一個位址定義具有不同路由類型的佇列,但這通常會導致反模式,因此不建議這麼做。 |
4. 自動配置
預設情況下,Apache ActiveMQ Artemis 會自動建立位址和佇列,以支援您正在使用的任何協定的語意。訊息代理程式了解如何使用核心資源來支援每個協定的功能,因此在大多數情況下不需要手動配置。這使您不必在用戶端連線之前預先配置每個位址和佇列。
訊息代理程式可以選擇性地設定為在不再使用時自動刪除位址和佇列。
自動建立和刪除是根據每個位址配置的,並由以下 address-setting
元素控制
-
auto-create-addresses
-
auto-delete-addresses
-
default-address-routing-type
-
auto-create-queues
-
auto-delete-queues
-
default-queue-routing-type
自動佇列建立適用於正常運作期間不會建立的佇列。例如,當遠端應用程式在 JMS 主題上建立消費者時,將會建立一個代表該訂閱的佇列,如 JMS 到核心對應章節中所述。無論 auto-create-queues 如何設定,都會建立此佇列,因為它是正常運作所必需的。 |
有關這些元素的更多詳細資訊,請參閱有關位址設定的文件。
當然,可以停用自動配置,並且可以手動配置所有內容。繼續閱讀以取得有關手動配置的更多詳細資訊。
5. 基本手動配置
以下範例示範如何為基本 anycast 和 multicast 使用案例配置資源。
這些使用案例的許多細節與協定無關。此處的目標是示範和說明基本配置元素以及位址模型的基本運作方式。 |
5.1. Anycast
anycast 語意最常見的使用案例,有時稱為點對點,涉及應用程式遵循「競爭消費者」模式從共用佇列接收訊息。接收訊息的消費者越多,整體訊息吞吐量就越大。多個 Java 應用程式共用一個 JMS 佇列是此使用案例的經典範例。
在此使用案例中,訊息代理程式配置為,例如,使用 anycast
路由類型和只有一個佇列 q1
的位址 address.foo
。當生產者傳送訊息至 address.foo
時,訊息會被路由至 q1
,最後分派給其中一個消費者。
圖 1. Anycast
以下是此使用案例在 etc/broker.xml
中的配置方式
<addresses>
<address name="address.foo">
<anycast>
<queue name="q1"/>
</anycast>
</address>
</addresses>
對於大多數支援此類使用案例的協定和 API(例如,JMS、AMQP 等),習慣上在傳送和消費訊息時使用相同的名稱。在這種情況下,您會使用如下的配置
<addresses>
<address name="orderQueue">
<anycast>
<queue name="orderQueue"/>
</anycast>
</address>
</addresses>
5.2. Multicast
multicast 語意最常見的使用案例,有時稱為發布/訂閱或「pub/sub」,涉及每個應用程式接收傳送至位址的每個訊息。從 JMS 主題消費訊息的多個應用程式是此使用案例的經典範例。MQTT 訂閱是另一個支援 multicast 語意的範例。
在此使用案例中,訊息代理程式配置為使用 multicast
路由類型和兩個佇列 q1
和 q2
的位址 address.foo
。當生產者傳送訊息至 address.foo
時,訊息會被路由至兩者 q1
和 q2
,以便最終兩個消費者都收到相同的訊息。
圖 2. Multicast
以下是此使用案例在 etc/broker.xml
中的配置方式
<addresses>
<address name="address.foo">
<multicast>
<queue name="q1"/>
<queue name="q2"/>
</multicast>
</address>
</addresses>
此基本配置簡單明瞭,但有一個問題。在正常的 pub/sub 使用案例中,例如使用 JMS 主題或 MQTT,訂閱者的數量不是事先知道的。在這種情況下,這是建議的配置
<addresses>
<address name="address.foo">
<multicast/>
</address>
</addresses>
定義不帶任何佇列的 <multicast/>
,當消費者連線至 address.foo
時,訊息代理程式會自動為每個訂閱建立佇列。然後,當訊息傳送至 address.foo
時,它會被路由至每個訂閱者的每個佇列,因此每個訂閱者都會收到每個訊息。這些佇列通常被稱為訂閱佇列,原因很明顯。
這些訂閱佇列通常會根據用於建立它們的協定的語意來命名。例如,JMS 支援持久性和非持久性訂閱。非持久性訂閱的佇列會使用 UUID 命名,但用於持久性訂閱的佇列會根據 JMS「用戶端 ID」和「訂閱名稱」來命名。類似的慣例也適用於 AMQP、MQTT、STOMP 等。
6. 進階手動配置
6.1. 完整限定佇列名稱
在大多數情況下,不需要或不希望靜態配置上述訂閱佇列。但是,在某些情況下,使用者可能希望靜態配置訂閱佇列,並稍後使用完整限定佇列名稱 (FQQN) 直接連線至該佇列。
FQQN 使用特殊的語法來指定位址和佇列,以便使用不原生理解位址/佇列分離的協定和 API(例如,AMQP、JMS 等)的應用程式可以將訊息傳送或訂閱直接到佇列,而不僅限於位址。應用程式只需要使用以 ::
分隔的位址名稱和佇列名稱(例如,address::queue
)。
在此範例中,位址 a1
配置了兩個佇列:q1
、q2
,如下列配置所示。
<addresses>
<address name="a1">
<multicast>
<queue name="q1" />
<queue name="q2" />
</multicast>
</address>
</addresses>
以下是使用 JMS 的 Java 程式碼片段,示範 FQQN 語法
Queue q1 session.createQueue("a1::q1");
MessageConsumer consumer = session.createConsumer(q1);
字串 :: 只能用於 FQQN,不得用於位址或佇列名稱中的任何其他上下文。 |
以下範例示範如何使用訊息代理程式端配置來靜態配置佇列,並提供共用、非共用、持久性和非持久性訂閱行為的發布訂閱行為。
6.1.1. 使用 max-consumers
的共用、持久性訂閱佇列
佇列的預設行為是不限制連線的佇列消費者的數量。佇列元素的 max-consumers
參數可用於限制任何時間允許的連線消費者數量。
開啟檔案 etc/broker.xml
進行編輯。
<addresses>
<address name="durable.foo">
<multicast>
<!-- pre-configured shared durable subscription queue -->
<queue name="q1" max-consumers="10">
<durable>true</durable>
</queue>
</multicast>
</address>
</addresses>
6.1.2. 非共用、持久性訂閱佇列
可以配置訊息代理程式以防止一次有多個消費者連線到佇列。因此,以這種方式配置的佇列訂閱是「非共用」的。若要執行此操作,只需將 max-consumers
參數設定為 1
<addresses>
<address name="durable.foo">
<multicast>
<!-- pre-configured non shared durable subscription queue -->
<queue name="q1" max-consumers="1">
<durable>true</durable>
</queue>
</multicast>
</address>
</addresses>
6.1.3. 非持久性訂閱佇列
非持久性訂閱通常再次由相關的協定管理員管理,方法是建立和刪除暫時佇列。
如果使用者需要預先建立一個行為類似於非持久性訂閱佇列的佇列,則可以在佇列上啟用 purge-on-no-consumers
旗標。當 purge-on-no-consumers
設定為 true
時。佇列將在附加消費者之前不會開始接收訊息。當最後一個消費者從佇列分離時。佇列會被清除(其訊息會被移除),並且在附加新的消費者之前不會再接收任何訊息。
開啟檔案 etc/broker.xml
進行編輯。
<addresses>
<address name="non.shared.durable.foo">
<multicast>
<queue name="orders1" purge-on-no-consumers="true"/>
</multicast>
</address>
</addresses>
6.2. 已停用佇列
如果使用者需要靜態配置佇列並停用路由到該佇列,例如在需要定義佇列以便消費者可以繫結的情況下,但您希望暫時停用路由到該佇列的訊息。
或者您需要停止訊息流向佇列以允許調查,同時保持消費者繫結,但不希望將更多訊息路由到佇列以避免訊息累積。
當 enabled
設定為 true
時,佇列將會被路由訊息。(預設)
當 enabled
設定為 false
時,佇列將不會被路由訊息。
開啟檔案 etc/broker.xml
進行編輯。
<addresses>
<address name="foo.bar">
<multicast>
<queue name="orders1" enabled="false"/>
</multicast>
</address>
</addresses>
停用位址上的所有佇列意味著傳送至該位址的任何訊息都將會被靜默捨棄。 |
6.3. 暫時佇列
對於某些僅支援沒有位址/佇列分離的單體「目的地」的協定和 API(例如,AMQP、JMS 等),訊息代理程式會使用 UUID(即通用唯一識別碼)作為位址和佇列的名稱來建立暫時佇列。由於名稱是 UUID,因此不可能為其建立 address-setting
,其 match
除了 #
之外的任何內容。
要解決此問題,可以在 broker.xml
中指定 temporary-queue-namespace
,然後建立一個 address-setting
,其 match
值對應於已設定的 temporary-queue-namespace
。當設定了 temporary-queue-namespace
並且建立臨時佇列時,訊息代理程式會將 temporary-queue-namespace
值以及在 wildcard-addresses
中設定的 delimiter
值(預設為 .
)附加到位址名稱的前面,並使用該名稱來查詢相關聯的 address-setting
值。
這是一個簡單的設定範例
<temporary-queue-namespace>temp</temporary-queue-namespace>
<address-settings>
<address-setting match="temp.#">
<enable-metrics>false</enable-metrics>
</address-setting>
</address-settings>
使用此設定,任何臨時佇列都會停用指標。
此設定並不會變更臨時佇列的實際名稱。它只會變更用於查詢位址設定的名稱。 |
7. 如何篩選訊息
Apache ActiveMQ Artemis 支援使用 篩選表示式來篩選訊息的功能。
篩選器可以應用在兩個位置 - 佇列和消費者上。
在佇列上篩選訊息比在消費者上篩選訊息可提高效能,因為不需要掃描訊息。但是,佇列篩選器通常不如靈活。
7.1. 佇列篩選器
當篩選器應用於佇列時,訊息會在路由至佇列之前進行篩選。若要新增篩選器,請在設定佇列時使用 filter
元素,例如:
<addresses>
<address name="filter">
<anycast>
<queue name="filter">
<filter string="color='red'"/>
</queue>
</anycast>
</address>
</addresses>
上面定義的篩選器確保只有具有屬性 "color='red'"
的訊息才會傳送到此佇列。
7.2. 消費者篩選器
消費者篩選器是在訊息路由至佇列之後應用,並且使用適當的用戶端 API 定義。以下 JMS 範例顯示消費者篩選器如何運作。
定義一個具有單一佇列的位址,在 etc/broker.xml
中沒有應用篩選器。
<addresses>
<address name="filter">
<anycast>
<queue name="filter"/>
</anycast>
</address>
</addresses>
然後將一些訊息傳送到佇列。
...
// Send some messages
for (int i = 0; i < 3; i ++) {
TextMessage redMessage = senderSession.createTextMessage("Red");
redMessage.setStringProperty("color", "red");
producer.send(redMessage)
TextMessage greenMessage = senderSession.createTextMessage("Green");
greenMessage.setStringProperty("color", "green");
producer.send(greenMessage)
}
此時,佇列將有 6 個訊息:紅色、綠色、紅色、綠色、紅色、綠色。
建立一個具有篩選器 color='red'
的消費者。
MessageConsumer redConsumer = redSession.createConsumer(queue, "color='red'");
redConsumer
有一個篩選器,只比對「紅色」訊息。redConsumer
將接收 3 個訊息。
red, red, red
產生的佇列現在會是
green, green, green
8. 決定路由類型的替代方法
通常,路由類型是由靜態 XML 設定或用於 自動位址和佇列建立的 default-address-routing-type
和 default-queue-routing-type
address-setting
元素決定。但是,還有兩種其他方式可以指定路由類型
-
用戶端應用程式在傳送訊息或建立消費者時可以使用的可設定前綴
-
用戶端應用程式可以在傳送的訊息上設定的屬性
8.1. 使用前綴決定路由類型
這些前綴是使用用戶端使用的 acceptor
的 URL 內的 anycastPrefix
和 multicastPrefix
參數設定的。當需要多個值時,可以用逗號分隔。
8.1.1. 設定單播前綴
在 etc/broker.xml
中,將 anycastPrefix
新增到所需 acceptor
的 URL 中。在下面的範例中,設定接受器使用 queue/
作為 anycastPrefix
。如果用戶端想要單播路由,則用戶端程式碼可以指定 queue/foo/
。
<acceptor name="artemis">tcp://0.0.0.0:61616?protocols=AMQP;anycastPrefix=queue/</acceptor>
例如,考慮一個想要使用單播語意將訊息傳送到不存在的佇列的 STOMP 用戶端。另請考慮訊息代理程式設定為自動建立位址和佇列,但 default-address-routing-type
和 default-queue-routing-type
都是 MULTICAST
。由於 anycastPrefix
是 queue/
,因此它可以只將訊息傳送到 queue/foo
,訊息代理程式會自動建立一個名為 foo
的位址,並使用也名為 foo
的單播佇列。
8.1.2. 設定多播前綴
在 etc/broker.xml
中,將 multicastPrefix
新增到所需 acceptor
的 URL 中。在下面的範例中,設定接受器使用 topic/
作為 multicastPrefix
。如果用戶端想要多播路由,則用戶端程式碼可以指定 topic/foo/
。
<acceptor name="artemis">tcp://0.0.0.0:61616?protocols=AMQP;multicastPrefix=topic/</acceptor>
例如,考慮一個想要在不存在的位址上建立具有多播語意的訂閱的 STOMP 用戶端。另請考慮訊息代理程式設定為自動建立位址和佇列,但 default-address-routing-type
和 default-queue-routing-type
都是 ANYCAST
。由於 multicastPrefix
是 topic/
,因此它可以只訂閱 topic/foo
,訊息代理程式會自動建立一個名為 foo
的位址,並為訂閱建立一個多播佇列。然後,傳送到 foo
的任何訊息都將路由到訂閱佇列。
8.2. 使用訊息屬性決定路由類型
AMQ_ROUTING_TYPE
屬性代表一個 byte
值,訊息代理程式在傳送訊息時將使用它來決定路由類型。使用 0
表示單播路由,或使用 1
表示多播路由。
訊息只會路由到符合其 _AMQ_ROUTING_TYPE 屬性值(如果有的話)的佇列。例如,如果將 _AMQ_ROUTING_TYPE 值為 1 (即多播)的訊息傳送到只有單播佇列的位址,則訊息實際上不會路由到任何佇列,因為路由類型不符。如果未設定 _AMQ_ROUTING_TYPE ,則訊息將根據佇列的路由語意路由到位址上的所有佇列。 |