RabbitMQ is a widely used message broker for asynchronous task processing. In Laravel, we can integrate it as a queue driver to improve application performance and scalability.
Setting Up RabbitMQ in Laravel
Installing the Package
Laravel does not have native support for RabbitMQ, so we use a third-party package like vladimir-yuldashev/laravel-queue-rabbitmq.
composer require vladimir-yuldashev/laravel-queue-rabbitmq
Configuring config/queue.php
Add the RabbitMQ configuration:
<?php
'connections' => [
'rabbitmq' => [
'driver' => 'rabbitmq',
'hosts' => [
[
'host' => env('RABBITMQ_HOST', '127.0.0.1'),
'port' => env('RABBITMQ_PORT', 5672),
'user' => env('RABBITMQ_USER', 'guest'),
'password' => env('RABBITMQ_PASSWORD', 'guest'),
'vhost' => env('RABBITMQ_VHOST', '/'),
],
],
'queue' => env('RABBITMQ_QUEUE', 'default'),
'options' => [
'exchange' => [
'name' => env('RABBITMQ_EXCHANGE_NAME', 'default'),
'type' => env('RABBITMQ_EXCHANGE_TYPE', 'direct'),
],
],
],
],
Setting Env Vars
Add to .env
:
QUEUE_CONNECTION=rabbitmq
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_VHOST=/
RABBITMQ_QUEUE=default
Creating and Dispatching Jobs
php artisan make:job ProcessPayment
Edit app/Jobs/ProcessPayment.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldBeQueued;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;
use App\Models\Inventory;
use App\Services\PaymentService;
use App\Exceptions\PaymentFailedException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ProcessPayment implements ShouldBeQueued
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3; // The maximum number of times this job will be attempted
public $backoff = [10, 30, 60]; // The delay in seconds before retrying the job after a failure, increasing with each attempt
protected $paymentService;
// Constructor to inject the PaymentService dependency
public function __construct(PaymentService $paymentService)
{
$this->paymentService = $paymentService;
}
// This method contains the main logic to process the payment
public function handle()
{
try {
// Step 1: Process the payment using the injected PaymentService
$paymentSuccess = $this->paymentService->process();
// If the payment was not successful, throw a specific exception
if (!$paymentSuccess) {
throw new PaymentFailedException('Payment processing failed.');
}
// Step 2 & 3: Create the order and deduct inventory within a database transaction to ensure data consistency
DB::transaction(function () {
// Check if an order with the same criteria already exists to ensure idempotency
$order = Order::firstOrCreate([
'user_id' => 1,
// Add other unique criteria for identifying existing orders
], [
'status' => 'paid',
'total' => 100.00,
]);
// Deduct the item quantity from the inventory (consider using optimistic/pessimistic locking for concurrency)
Inventory::where('product_id', 1)->decrement('quantity', 1);
});
} catch (PaymentFailedException $e) {
// Specific handling for payment failures
Log::warning('Payment failure while processing order: ' . $e->getMessage());
throw $e; // Re-throw the exception to trigger the retry mechanism
} catch (Exception $e) {
// Handling for unexpected errors
Log::error('Unexpected error while processing order: ' . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
throw $e; // Re-throw the exception to trigger the retry mechanism
}
}
// This method is executed if the job fails after all retry attempts
public function failed(Exception $exception)
{
// Logic to be executed when the job completely fails
Log::critical('Critical failure while processing order after multiple retries: ' . $exception->getMessage(), ['trace' => $exception->getTraceAsString()]);
// Consider sending notifications to support teams or implementing other failure handling strategies
}
}
Dispatching a Job
use App\Jobs\ProcessPayment;
ProcessPayment::dispatch();
Running the worker
php artisan queue:work rabbitmq
Conclusion
By integrating RabbitMQ with Laravel queues, you can significantly enhance your application's performance by offloading time-consuming tasks to background processing. This results in a more responsive user experience and improved system scalability.
With features like automatic retries and message durability, RabbitMQ ensures that tasks are processed reliably even in case of failures. Using Laravel's queue system with RabbitMQ allows you to build robust and efficient workflows, making it an excellent choice for large-scale applications requiring asynchronous processing.
For more details, refer to the official documentation:
Top comments (4)
I checked the package and it hasn't been updated since may 2024. In that time a new major version (4) came out with an update of the protocol (1). The version 3 releases are unsupported.
This makes me a bit wary about using the package.
Thanks David, I recommend using any package with caution.
Another package I use is php-amqplib/php-amqplib you can analyze it is quite large.
Hello Aleson , thank you for sharing this , have you thought of integrating with Kafka and would you recommend rabbitmq or kafka for someone who is using horizon with redis ? to scale and ensure no message goes unaddressed. Is rabbitmq for large scale application or one should look for kafka ?
Depending on what you want, rabbit and kafka are totally different. Try to analyze the differences and see what you really need.