队列使用先进先出 (FIFO) 语义,这意味着消息通常添加到队列的“尾部”并从“头部”移除。“环形”队列是一种特殊类型的队列,具有固定大小。通过在队列消息数量达到配置大小后移除队列头部消息来维护固定大小。

例如,考虑一个配置为环形大小为 3 的队列和一个按顺序发送消息ABCD 的生产者。一旦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. 配置

与环形队列配置相关的参数有两个。

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. 传输中的消息和回滚

当消息处于“传输中”状态时,它们处于一种中间状态,即它们不属于队列,但也没有被确认。代理取决于消费者来确认或不确认这些消息。在环形队列的上下文中,处于传输中的消息不能从队列中移除。

这会导致一些难题。

由于传输中消息的性质,客户端实际上可以向环形队列发送比允许的数量更多的消息。这可能让人觉得环形大小没有得到正确执行。考虑以下简单场景

  • 队列fooring-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

现在foomessageCount为 4,比ring-size 3 大 1!但是,代理别无选择,只能允许这样做,因为它不能移除处于传输状态的消息。

现在假设消费者在没有实际确认这 4 条消息的情况下关闭了。这 4 条处于传输状态的未确认消息将被取消回代理,并按与它们被消费的相反顺序添加到队列的头部。这当然会使队列超过其配置的ring-size。因此,由于环形队列更喜欢队列尾部的消息而不是头部的消息,它将保留BCD,并删除A(因为A是最后添加到队列头部的消息)。

事务或核心会话回滚的处理方式相同。

如果希望避免此类情况,并且正在直接使用核心客户端或核心 JMS 客户端,则可以通过减少consumerWindowSize的大小(默认值为 1024 * 1024 字节)来最小化传输中的消息数量。

3. 计划消息

当计划消息被发送到队列时,它不会像普通消息那样立即被添加到队列尾部。它被保存在一个中间缓冲区中,并根据消息的详细信息安排在队列的头部传递。但是,计划消息仍然反映在队列的消息计数中。与处于传输中的消息一样,这可能让人觉得环形队列的大小没有得到正确执行。考虑以下简单场景

  • 队列fooring-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

现在foomessageCount为 4,比ring-size 3 大 1!但是,计划消息实际上还没有在队列中(即它在代理中,并计划被放入队列中)。当 12:05 的计划传递时间到达时,消息将被放入队列头部,但由于环形队列的大小已经达到,计划消息A将被移除。

4. 分页

与计划消息和传输中的消息类似,分页消息不会计入环形队列的大小,因为消息实际上是在地址级别分页,而不是在队列级别分页。分页消息实际上不属于队列,尽管它反映在队列的messageCount中。

建议不要对带有环形队列的地址使用分页。换句话说,请确保整个地址都能够放入内存,或者使用DROPBLOCKFAIL address-full-policy