MQ consumers generally obtain messages in one of two ways: push or pull. The names describe who takes the initiative. In push mode, the broker sends messages to the consumer as soon as they arrive. In pull mode, the consumer asks the broker for messages on its own schedule.

Push: fast, but easy to overload

Push consumption model

In a push model, the MQ actively delivers messages to consumers. The biggest advantage is responsiveness: once the broker receives a message, it can send it to the consumer almost immediately, so real-time behavior is strong.

The drawback appears when traffic spikes. Consumers cannot directly control how fast the broker pushes messages. If the message volume is high, the consumer can be overwhelmed.

Pull: control, at the cost of latency

Pull consumption model

Pull mode reverses the direction. The consumer goes to the MQ and fetches messages itself. That gives the consumer control: it can decide when to fetch based on its own capacity, so pressure is easier to manage.

The tradeoff is latency. The consumer has to choose a pull interval, and that interval affects how quickly new messages are processed.

A simple delivery analogy captures the difference. The message is a package, and the consumer is the person waiting for it. A courier can bring it to the door, which is push, or the person can go to the pickup station, which is pull.

RocketMQ supports both, but push is the common choice

RocketMQ, the high-performance and feature-rich MQ open-sourced by Alibaba, implements both consumption modes. Applications can choose push or pull.

Push mode implementation Pull mode implementation

In practice, most projects use push. Pull has weaker real-time behavior, and it also forces developers to maintain consumption progress themselves, which adds work.

That makes the push implementation worth examining. The interesting part is how RocketMQ gets push-like responsiveness without giving up the consumer-side control that pull naturally provides.

RocketMQ's push is a clever pseudo-push

Push usually means the broker sends messages, while pull means the consumer asks for them. RocketMQ keeps the push programming model but implements the underlying behavior with pull. That is why it can be called a pseudo-push model.

RocketMQ balances push and pull Clarifying pseudo-push

The consumer still initiates requests, but RocketMQ wraps the process so that application code feels as if messages are being pushed. Since the foundation is pull, the consumer can control pressure. The remaining problem is how to make pull feel real-time.

The answer is long polling.

Polling and long polling

Both polling and long polling are pull-based: the client sends requests to the server to fetch data. In MQ terms, the consumer asks the broker for messages.

Polling

Polling model

With ordinary polling, the client asks for data at fixed intervals regardless of whether the server has anything new. Sometimes it gets data; sometimes it gets nothing.

Using the delivery analogy again: the package has not reached the pickup station yet, but the person is impatient and walks over every five minutes to ask whether it has arrived. That is polling.

This creates two problems. First, the consumer keeps sending requests even when there are no messages, which wastes server memory and bandwidth. Second, data can be delayed: if a message arrives just after a request returns empty, the consumer may not see it until the next polling interval.

Long polling

Long polling model

Long polling changes the waiting behavior. If the server has data, it returns it immediately. If the server has no data, it does not respond right away; it holds the request. When new data arrives, the server responds and returns the data.

In the package analogy, the person does not go home empty-handed and come back later. Instead, the person waits at the pickup station until the package arrives, then takes it home. That saves repeated trips and still gets the package as soon as possible.

Long polling solves the empty-request problem and improves real-time delivery. When new data arrives, the consumer can receive it almost immediately, so the effect is close to push. The cost is more complex server-side logic, but the benefit usually outweighs it.

Inside RocketMQ's push consumer

Consumer pull-message flow

The push consumer relies on a background thread that processes pull requests. The flow looks roughly like this:

  1. The consumer has a background thread that handles PullRequest work.
  2. Before pulling, it checks whether too many messages are already waiting to be consumed. If the backlog is too large, it waits for a while and then checks again.
  3. If the backlog is acceptable, the consumer sends a pull request to the MQ. If messages exist, they are returned. If not, the broker holds the request until a new message arrives.
  4. After receiving messages, the consumer invokes the user-defined message-handling logic, the MessageListener implementation, and then starts another pull cycle.

Controlling pressure before pulling

Backpressure control logic

RocketMQ checks consumer pressure before fetching messages. It uses two kinds of thresholds: the number of messages that have not yet been consumed, and the memory size occupied by those unconsumed messages.

If either threshold is exceeded, the consumer does not pull immediately. It waits and retries the pull logic later. If pressure is still high, it keeps waiting. Only when the backlog falls below the configured threshold does it actually send a request to the MQ.

This is the backpressure mechanism. It preserves the push-style API while preventing a slow consumer from being flooded.

Holding requests on the broker

Request hold logic

When the broker cannot find messages for a pull request, it does not simply return an empty response. It suspends the request and stores it.

The broker uses PullRequestHoldService to manage these suspended requests. The suspendPullRequest method stores requests through ManyPullRequest and a ConcurrentHashMap.

PullRequestHoldService structure

This is the long-polling part of the implementation: the request stays pending until there is something to return.

Waking consumers when messages arrive

Message arrival listener

When a producer sends a message to the broker, the broker calls NotifyMessageArrivingListener.arriving. That triggers PullRequestHoldService.notifyMessageArriving, and the broker reprocesses the relevant pull request.

Notify and return logic

At that point, the newly arrived message can be found, and the response is sent back to the consumer over the network. The consumer receives the message and runs its listener, and the cycle continues.

What this design teaches

RocketMQ's push consumer is not a pure server-initiated push. It is a pull-based design wrapped in a push-style API, with long polling providing real-time behavior and backpressure keeping consumers from being overwhelmed.

The same ideas appear outside MQ systems. Nacos, for example, also uses push-like behavior and long polling. The concrete implementation differs from product to product, but the patterns are often similar. Once these concepts are clear, reading other frameworks becomes much less mysterious.