佇列的操作方式為先進先出(FIFO),這表示訊息通常會加入佇列的「尾部」,並從「頭部」移除。「環形」佇列是一種特殊的佇列,其大小是固定的。當佇列中的訊息數量達到設定的大小時,會移除佇列頭部的訊息,以此來維持固定的大小。
舉例來說,考慮一個設定環形大小為 3 的佇列,以及一個依序傳送訊息 A
、B
、C
和 D
的生產者。一旦傳送 C
,佇列中的訊息數量將會是 3,與設定的環形大小相同。我們可以這樣視覺化佇列的成長…
傳送 A
之後
|---| head/tail -> | A | |---|
傳送 B
之後
|---| head -> | A | |---| tail -> | B | |---|
傳送 C
之後
|---| head -> | A | |---| | B | |---| tail -> | C | |---|
當傳送 D
時,它會加入佇列的尾部,而佇列頭部的訊息(即 A
)將會被移除,所以佇列會像這樣
|---| head -> | B | |---| | C | |---| tail -> | D | |---|
這個範例涵蓋了最基本的使用情況,也就是將訊息加入佇列的尾部。然而,還有其他一些重要的使用情況涉及
-
傳送中的訊息與回滾
-
排程訊息
-
分頁
但是,在我們討論這些使用情況之前,讓我們先看看環形佇列的基本設定。
1. 設定
有 2 個與環形佇列設定相關的參數。
ring-size
參數可以直接在 queue
元素上設定。預設值來自 default-ring-size
address-setting
(見下方)。
<addresses>
<address name="myRing">
<anycast>
<queue name="myRing" ring-size="3" />
</anycast>
</address>
</addresses>
default-ring-size
是一個 address-setting
,它適用於沒有明確設定 ring-size
的匹配位址上的佇列。這對於自動建立的佇列特別有用。預設值為 -1
(即沒有限制)。
<address-settings>
<address-setting match="ring.#">
<default-ring-size>3</default-ring-size>
</address-setting>
</address-settings>
ring-size
可以在執行階段更新。如果新的 ring-size
設定低於先前的 ring-size
,代理程式不會立即從佇列頭部刪除足夠的訊息來強制執行新的大小。傳送到佇列的新訊息將會強制刪除舊訊息(即佇列不會變得更大),但是佇列要等到透過用戶端正常消費訊息自然達到其新大小為止。
2. 傳送中的訊息與回滾
當訊息「傳送中」時,它們處於一種中間狀態,技術上它們不在佇列中,但也尚未被確認。代理程式取決於消費者是否要確認這些訊息。在環形佇列的內容中,傳送中的訊息無法從佇列中移除。
這會產生一些難題。
由於傳送中訊息的性質,用戶端實際上可以傳送比環形佇列允許的更多的訊息。這可能會讓人覺得環形大小沒有被正確執行。考慮這個簡單的場景
-
佇列
foo
的ring-size="3"
-
佇列
foo
上有 1 個消費者 -
訊息
A
傳送到foo
並派送給消費者 -
messageCount
=1,deliveringCount
=1 -
訊息
B
傳送到foo
並派送給消費者 -
messageCount
=2,deliveringCount
=2 -
訊息
C
傳送到foo
並派送給消費者 -
messageCount
=3,deliveringCount
=3 -
訊息
D
傳送到foo
並派送給消費者 -
messageCount
=4,deliveringCount
=4
foo
的 messageCount
現在是 4,比 ring-size
3 大 1!但是,代理程式別無選擇,只能允許這樣做,因為它無法從傳送中的佇列中移除訊息。
現在考慮在沒有實際確認任何這 4 個訊息的情況下關閉消費者。這 4 個傳送中、未確認的訊息將會取消回代理程式,並以與它們被消費的相反順序加入佇列的頭部。當然,這會使佇列超過設定的 ring-size
。因此,由於環形佇列偏好佇列尾部的訊息勝過佇列頭部的訊息,它將會保留 B
、C
和 D
並刪除 A
(因為 A
是最後一個加入佇列頭部的訊息)。
事務或核心階段回滾的處理方式相同。
如果您希望避免這種情況,並且您直接使用核心用戶端或核心 JMS 用戶端,您可以透過縮小 consumerWindowSize
的大小來減少傳送中的訊息(預設為 1024 * 1024 位元組)。
3. 排程訊息
當排程訊息傳送到佇列時,它不會像一般訊息一樣立即加入佇列的尾部。它會被保存在一個中間緩衝區中,並根據訊息的詳細資訊排程傳送到佇列的頭部。然而,排程訊息仍然會反映在佇列的訊息計數中。與傳送中的訊息一樣,這可能會讓人覺得環形佇列的大小沒有被執行。考慮這個簡單的場景
-
佇列
foo
的ring-size="3"
-
在 12:00,訊息
A
傳送到foo
,排程在 12:05 -
messageCount
=1,scheduledCount
=1 -
在 12:01,訊息
B
傳送到foo
-
messageCount
=2,scheduledCount
=1 -
在 12:02,訊息
C
傳送到foo
-
messageCount
=3,scheduledCount
=1 -
在 12:03,訊息
D
傳送到foo
-
messageCount
=4,scheduledCount
=1
foo
的 messageCount
現在是 4,比 ring-size
3 大 1!但是,排程訊息在技術上尚未在佇列上(即它在代理程式上並排程要放入佇列)。當 12:05 的排程傳送時間到來時,訊息將會放入佇列的頭部,但是由於環形佇列的大小已經達到,排程訊息 A
將會被移除。
4. 分頁
與排程訊息和傳送中的訊息類似,分頁訊息不會計入環形佇列的大小,因為訊息實際上是在位址層級而不是佇列層級進行分頁的。分頁訊息在技術上不在佇列上,儘管它會反映在佇列的 messageCount
中。
建議不要將分頁用於具有環形佇列的位址。換句話說,請確保整個位址能夠放入記憶體,或使用 DROP
、BLOCK
或 FAIL
address-full-policy
。