rabbitmq / rabbitmq-server

Open source RabbitMQ: core server and tier 1 (built-in) plugins
https://www.rabbitmq.com/
Other
11.84k stars 3.9k forks source link

Classic queue removes consumer #11574

Closed ansd closed 4 days ago

ansd commented 5 days ago

What?

The rabbit_queue_type API has allowed to cancel a consumer. Cancelling a consumer merely stops the queue sending more messages to the consumer. However, messages checked out to the cancelled consumer remain checked out until acked by the client. It's up to the client when and whether it wants to ack the remaining checked out messages.

For AMQP 0.9.1, this behaviour is necessary because the client may receive messages between the point in time it sends the basic.cancel request and the point in time it receives the basic.cancel_ok response.

AMQP 1.0 is a better designed protocol because a receiver can stop a link as shown in figure 2.46. After a link is stopped, the client knows that the queue won't deliver any more messages. Once the link is stopped, the client can subsequently detach the link.

This commit extends the rabbit_queue_type API to allow a consumer being immediately removed:

  1. Any checked out messages to that receiver will be requeued by the queue. (As explained previously, a client that first stops a link by sending a FLOW with link_credit=0 and echo=true and waiting for the FLOW reply from the server and settles any messages before it sends the DETACH frame, won't have any checked out messages).
  2. The queue entirely forgets this consumer and therefore stops delivering messages to the receiver.

This new behaviour of consumer removal is similar to what happens when an AMQP 0.9.1 channel is closed: All checked out messages to that channel will be requeued.

Why?

Removing the consumer immediately simplifies many aspects:

  1. The server session process doesn't need to requeue any checked out messages for the receiver when the receiver detaches the link. Specifically, messages in the outgoing_unsettled_map and outgoing_pending queue don't need to be requeued because the queue takes care of requeueing any checked out messages.
  2. It simplifies reasoning about clients first detaching and then re-attaching in the same session with the same link handle (the handle becomes available for re-use once a link is closed): This will result in the same RabbitMQ queue consumer tag.
  3. It simplifies queue implementations since state needs to be hold and special logic needs to be applied to consumers that are only cancelled (basic.cancel AMQP 0.9.1) but not removed.
  4. It makes the single active consumer feature safer when it comes to maintaining message order: If a client cancels consumption via AMQP 0.9.1 basic.cancel, but still has in-flight checked out messages, the queue will activate the next consumer. If the AMQP 0.9.1 client shortly after crashes, messages to the old consumer will be requeued which results in messages being out of order. To maintain message order, an AMQP 0.9.1 client must close the whole channel such that messages are requeued before the next consumer is activated. For AMQP 1.0, the client can either stop the link first (preferred) or detach the link directly. Detaching the link will requeue all messages before activating the next consumer, therefore maintaining message order. In this sense, the AMQP 1.0 implementation allows for the single active consumer to gracefully handoff consumption to the next consumer. Even if the session crashes, message order will be maintained.

    How?

rabbit_queue_type:cancel accepts a spec as argument. The new interaction between session proc and classic queue proc (let's call it cancel API v2) must be hidden behind a feature flag. This commit re-uses feature flag credit_api_v2 since it also gets introduced in 4.0.