Consuming Messages
The consume method allows you to process messages from a Redis queue. It will continuously wait for messages and process them using callback functions.
Basic Consumption
<?php
use ByJG\MessageQueueClient\Connector\ConnectorFactory;
use ByJG\MessageQueueClient\Connector\Pipe;
use ByJG\MessageQueueClient\Envelope;
use ByJG\MessageQueueClient\Message;
use ByJG\MessageQueueClient\RedisQueue\RedisQueueConnector;
use ByJG\Util\Uri;
// Register and create connector
ConnectorFactory::registerConnector(RedisQueueConnector::class);
$connector = ConnectorFactory::create(new Uri("redis://localhost:6379"));
// Create a queue
$pipe = new Pipe("test");
$pipe->withDeadLetter(new Pipe("dlq_test"));
// Start consuming
$connector->consume(
$pipe,
function (Envelope $envelope) {
echo "Processing message: " . $envelope->getMessage()->getBody();
return Message::ACK;
},
function (Envelope $envelope, $ex) {
echo "Error: " . $ex->getMessage();
return Message::REQUEUE;
}
);
Callback Functions
The consume method requires two callback functions:
Success Callback
Called when a message is successfully retrieved:
function (Envelope $envelope) {
// Access message body
$body = $envelope->getMessage()->getBody();
// Process the message
// ... your logic here ...
// Return action to take
return Message::ACK;
}
Error Callback
Called when an exception or error occurs during message processing:
function (Envelope $envelope, Exception|Error $ex) {
// Log the error
error_log($ex->getMessage());
// Access the message that caused the error
$body = $envelope->getMessage()->getBody();
// Return action to take
return Message::REQUEUE;
}
Return Values
Control message flow by returning one or more of these constants:
Single Actions
Message::ACK- Acknowledge the message and remove it from the processing listMessage::NACK- Not acknowledge the message. If a dead letter queue is configured, the message will be sent there. It is then removed from the processing listMessage::REQUEUE- Publish the message again (at the back of the queue) for reprocessing, then remove it from the processing listMessage::EXIT- Stop consuming and exit the consume loop
Combined Actions
You can combine actions using the bitwise OR operator (|):
// Acknowledge the message AND exit the consumer
return Message::ACK | Message::EXIT;
// Requeue the message AND exit the consumer
return Message::REQUEUE | Message::EXIT;
Examples
Simple Message Processing
$connector->consume(
$pipe,
function (Envelope $envelope) {
$data = json_decode($envelope->getMessage()->getBody(), true);
// Process data
processData($data);
return Message::ACK;
},
function (Envelope $envelope, $ex) {
error_log("Failed to process: " . $ex->getMessage());
return Message::NACK; // Send to dead letter queue
}
);
Processing with Exit Condition
$processedCount = 0;
$connector->consume(
$pipe,
function (Envelope $envelope) use (&$processedCount) {
$body = $envelope->getMessage()->getBody();
// Process message
processMessage($body);
$processedCount++;
// Exit after processing 100 messages
if ($processedCount >= 100) {
return Message::ACK | Message::EXIT;
}
return Message::ACK;
},
function (Envelope $envelope, $ex) {
error_log($ex->getMessage());
return Message::REQUEUE;
}
);
Conditional Requeuing
$connector->consume(
$pipe,
function (Envelope $envelope) {
$data = json_decode($envelope->getMessage()->getBody(), true);
if (shouldProcessNow($data)) {
processData($data);
return Message::ACK;
} else {
// Not ready to process, put it back
return Message::REQUEUE;
}
},
function (Envelope $envelope, $ex) {
// On error, send to dead letter queue
return Message::NACK;
}
);
Reliable Delivery
consume() uses the Redis reliable-queue pattern, so a consumer that dies (killed, crash, out of
memory) while handling a message does not lose it:
- The message is taken with
BLMOVE, which atomically moves it from the queue into a processing list. It is never only in the consumer's memory. - The callback runs and its outcome is applied: nothing for
ACK, publish to the dead letter queue forNACK, publish back to the queue forREQUEUE. - Only then is the message removed from the processing list (
LREM). - When
consume()starts, it moves everything left in its processing list back to the queue (one atomicLMOVEper message). Those messages are delivered next, in their original order.
The processing list is named after the queue:
consume() called with | Processing list |
|---|---|
no $identification | <queue>:processing |
$identification = "worker-1" | <queue>:processing:worker-1 |
A consumer that dies after the callback did its work but before step 3 leaves the message in the processing list, and it is delivered again once recovered. A message can therefore be processed more than once; make your handlers idempotent.
Running several consumers on the same queue
Recovery at start empties the whole processing list of that consumer. If several consumers of one
queue share the default <queue>:processing, a consumer that starts takes the in-flight messages of
the others still running and delivers them a second time.
So:
- One consumer per queue: nothing to do; the default processing list is fine.
- Several consumers per queue: give each one a distinct, stable identification (for example
worker-1,worker-2, the same after every restart). Each has its own processing list, and a consumer restarted under the same identification gets back what its previous run left there.
A processing list whose consumer never comes back under the same identification (for example, when
identifications are random or include a PID, or when you scale down) is not recovered automatically.
Find such lists with SCAN 0 MATCH <queue>:processing:* and push their content back with
LMOVE <list> <queue> LEFT RIGHT (or start a consumer with that identification).
Blocking Behavior
The consume() method uses Redis BLMOVE (blocking move from the right of the queue to the
processing list; requires Redis 6.2 or later):
- It will block and wait indefinitely until a message becomes available
- The timeout is set to 0, meaning it waits forever for the next message
- This is efficient as it doesn't poll the queue constantly
- The loop continues until you return
Message::EXITor the process is terminated
If the connection to Redis is lost while waiting, consume() does not throw. It drops the connection and
reconnects, waiting between attempts with a backoff that starts at 200ms and doubles up to 30 seconds
(randomised between half the delay and all of it). The backoff resets once a message is received.
The publish done for Message::NACK (to the dead letter queue) and Message::REQUEUE, and the removal
from the processing list, are retried the same way if the connection is lost, so the message that was
just received is not dropped.
Identification Parameter
The consume method accepts an optional $identification parameter. The Redis connector uses it to name
the consumer's processing list (<queue>:processing:<identification>), see
Running several consumers on the same queue:
$connector->consume($pipe, $onReceive, $onError, "worker-1");