Thông thường khi xây dựng ứng dụng với Laravel chúng ta thường làm dạng ứng dụng nguyên khối. Việc này gây ra nhiều hạn chế trong phát triển và mở rộng ứng dụng Laravel. Trong bài viết này hướng dẫn các bạn setup ứng dụng PHP Laravel dạng microservice và giao tiếp với nhau thông qua Rabbit MQ.
Bài viết này xuất phát từ nhu cầu thực tế khi CodeTuTam mong muốn tách nhỏ ứng dụng hiện tại của mình. Đương nhiên thì quá trình làm ban đầu không dễ dàng với những khái niệm còn khá nhiều bỡ ngỡ. Hi vọng bài viết này cũng sẽ giúp ích cho những bạn đang tìm kiếm giải pháp cho việc tối ưu hệ thống PHP Laravel của mình.
Như tên gọi của nó, ứng dụng nguyên khối là ứng dụng mà tất cả các chức năng đều nằm trong một dự án. Điều này có nghĩa là mỗi khi chúng ta thực hiện bất kỳ thay đổi nào (dù chỉ là một thay đổi nhỏ), chúng tôi sẽ triển khai lại toàn bộ ứng dụng của mình. Việc triển khai này khá bất tiện, cũng như tiềm ẩn nhiều rủi ro mang tính chất ảnh hưởng toàn bộ dự án.
Đặc biệt dự án quy mô lớn, việc duy trì, nâng cấp sẽ khó khăn hơn rất nhiều. Vì vậy, kiến trúc microservices ra đời để trợ giúp chúng ta có thể tách chức năng của nó thành một thành phần độc lập.
Vậy là tạm hiểu về ứng dụng nguyên khối và ứng dụng microservice rồi. Chúng ta sẽ tìm hiểu sơ lược qua thêm về RabbitMQ
Rabbitmq là một message broker – nó hoạt động như một người trung gian cho các dịch vụ của chúng ta (microservice). Nhiệm vụ của Rabbitmq là nhận tin nhắn từ một ứng dụng (producer) và chuyển chúng cho ứng dụng khác (consume) để thực hiện công việc.
Rabbitmq có phiên bản cloud tại https://www.cloudamqp.com hoặc các bạn cũng có thể cài đặt phiên bản docker của rabbitmq.
Trong bài viết này sẽ chỉ cho bạn cách sử dụng RabbitMQ cho giao tiếp giữa các service sử dụng Laravel. Điều tuyệt vời khi sử dụng Rabbitmq là nó sẽ xếp hàng các tin nhắn được gửi tới. Trong trường hợp người nhận đang bận hoặc bị ngắt kết nối ngay bây giờ, thì tin nhắn sẽ được lưu trữ bên trong Bộ lưu trữ RabbitMQ tạm thời. Các tin nhắn này sẽ được đẩy lại bất cứ khi nào người tiêu dùng/người nhận sẵn sàng.
Để thuận tiện bạn có thể sử dụng sẵn file docker compose đã được tạo sẵn dưới đây
version: "3.2" services: rabbitmq: image: rabbitmq:3-management hostname: 'rabbitmq' container_name: 'rabbitmq' ports: - 5672:5672 - 15672:15672 environment: - RABBITMQ_DEFAULT_USER=master_user_rabbit - RABBITMQ_DEFAULT_PASS=rabbit@13572468 volumes: - ./data/:/var/lib/rabbitmq/ - ./log/:/var/log/rabbitmq
Trong file docker-compose.yml này chúng ta sẽ cấu hình các port 5672 và 15672, trong đó port 15672 là port dùng để vào giao diện quản lý.
Ngoài ra cần cài đặt 2 biến môi trường là User và Password để đăng nhập vào màn hình quản lý của Rabbitmq.
Để tiến hành cài đặt, các bạn chạy lệnh
docker compose up
Chú ý rằng có thể thêm tham số -d để chạy ở chế độ ngầm
Sau khi cài đặt thành công các bạn có thể truy cập vào địa chỉ http://localhost:15672/ và đăng nhập với tài khoản đã tạo bên trên.
Chúng ta sẽ cần cài đặt 2 service để giả lập cho producer và consume. Để cài đặt Laravel chúng ta sử dụng lệnh như sau
// Tạo dự án 1 composer create-project laravel/laravel rabbitmq_laravel1 // tạo dự án 2 composer create-project laravel/laravel rabbitmq_laravel2
Sau khi khởi tạo dự án thành công, chúng ta cần cài thêm package để hỗ trợ kết nối với Rabbitmq
Github dự án cũng như các tài liệu hướng dẫn có thể xem thêm tại đây: https://github.com/vyuldashev/laravel-queue-rabbitmq
composer require vladimir-yuldashev/laravel-queue-rabbitmq
Sau khi cài đặt thành công chúng ta tiến hành cấu hình dự án, chú ý rằng việc cấu hình này là giống nhau trên cả 2 dự án (Service). Do vậy các bạn có thể tiến hành làm trọn vẹn 1 service trước clone ra service thứ 2.
QUEUE_CONNECTION=rabbitmq RABBITMQ_HOST= RABBITMQ_PORT=5672 RABBITMQ_USER=master_user_rabbit RABBITMQ_PASSWORD=rabbit@13572468 RABBITMQ_VHOST=laravel_consume
Chú ý điền đúng tài khoản và mật khẩu đã tạo, chỗ vhost bạn có thể điền tên bất kì nhưng hãy ghi nhớ nó vì chúng ta sẽ cần cấu hình trong màn hình quản lý của rabbitmq.
'rabbitmq' => [ 'driver' => 'rabbitmq', 'queue' => env('RABBITMQ_QUEUE', 'default'), 'connection' => PhpAmqpLib\Connection\AMQPLazyConnection::class, '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', '/'), ], ], 'options' => [ 'ssl_options' => [ 'cafile' => env('RABBITMQ_SSL_CAFILE', null), 'local_cert' => env('RABBITMQ_SSL_LOCALCERT', null), 'local_key' => env('RABBITMQ_SSL_LOCALKEY', null), 'verify_peer' => env('RABBITMQ_SSL_VERIFY_PEER', true), 'passphrase' => env('RABBITMQ_SSL_PASSPHRASE', null), ], 'queue' => [ 'job' => VladimirYuldashev\LaravelQueueRabbitMQ\Queue\Jobs\RabbitMQJob::class, // 'job' => App\Jobs\CustomHandleJob::class, ], ], /* * Set to "horizon" if you wish to use Laravel Horizon. */ 'worker' => env('RABBITMQ_WORKER', 'default'), ],
Chú ý tên queue hiện tại đang chưa khai báo trong env, do vậy sẽ là tên mặc định: default
Như vậy bạn đã cấu hình thành công 2 dự án laravel rồi, chúng ta sẽ cấu hình thêm 1 chút cho Rabbitmq
Cấu hình cho Rabbitmq
Đăng nhập vào màn hình quản lý của Rabbitmq tại http://localhost:15762 với tài khoản mật khẩu trước đó.
Thêm virtual host laravel_consume như sau
Sau khi thêm virtual host thành công, hãy thêm queue cho virtual host này. Như codetutam đã ghi bên trên, bạn đặt tên queue này là default.
Tại ứng dụng Laravel 1 và Laravel 2 chúng ta tạo ra 1 PingJob với nội dung lần lượt như sau
Bạn có thể sử dụng lệnh sau để tạo Job
php artisan make:job PingJob
File PingJob ở Laravel 1:
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class PingJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Create a new job instance. */ public function __construct() { // } /** * Execute the job. */ public function handle(): void { // } }
File PingJob.php ở Laravel 2
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class PingJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; /** * Create a new job instance. */ public function __construct() { // } /** * Execute the job. */ public function handle(): void { echo 'Ping Event Received' . PHP_EOL; } }
Tiếp đến ở Laravel 1, chúng ta tạo thêm 1 command với nội dung như sau
Tham khảo lệnh tạo command
php artisan make:command PingJobCommand
File PingJobCommand.php với nội dung như sau:
<?php namespace App\Console\Commands; use App\Jobs\PingJob; use Illuminate\Console\Command; class PingJobCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'ping:job'; /** * The console command description. * * @var string */ protected $description = 'Command description'; /** * Execute the console command. */ public function handle() { PingJob::dispatch(); } }
Chạy thử lệnh ping:job
php artisan ping:job
Đương nhiên khi này bên Service Laravel 2 sẽ không có thông tin gì, vì chúng ta cần phải đăng ký lắng nghe sự kiện nữa.
php artisan rabbitmq:consume
Đối với ứng dụng thực tế thì bạn có thể đẩy vào trong supervisord để thực hiện lệnh này.
Ngay sau khi chạy thành công, bạn sẽ nhận được 1 thông tin như sau:
Tiếp đến, chúng ta thử nghiệm với việc truyền sự kiện có tham số kèm theo
Tương tự như ví dụ trên chúng ta sẽ tạo ra Job UserCreated
php artisan make:job UserCreated
File UserCreated.php nội dung như sau
Laravel 1:
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class UserCreated implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $data; /** * Create a new job instance. */ public function __construct($data) { $this->data = $data; } /** * Execute the job. */ public function handle(): void { // } }
Laravel 2:
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class UserCreated implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; private $data; /** * Create a new job instance. */ public function __construct($data) { $this->data = $data; } /** * Execute the job. */ public function handle() { echo 'Event: UserCreated' . PHP_EOL; echo json_encode($this->data) . PHP_EOL; // TODO: Event User Created } }
Tạo file command như sau
<?php namespace App\Console\Commands; use App\Jobs\UserCreated; use Illuminate\Console\Command; class UserJobCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'ping:user'; /** * The console command description. * * @var string */ protected $description = 'Command description'; /** * Execute the console command. */ public function handle() { UserCreated::dispatch(["id"=>1,"name"=>"Hello","created_at"=>new \DateTime()]); } }
Khi chạy command php artisan ping:user ở bên laravel 1 thì bên laravel 2 sẽ nhận được như sau
Như các bạn đã thấy, chúng ta luôn phải tạo 2 file đồng thời ở cả 2 service. Việc này tạo ra sự bất tiện cho triển khai, để khắc phục điều này hãy xem kĩ hơn tại github vyuldashev/laravel-queue-rabbitmq. Trong Github đã nêu rõ cách xử lý vấn đề này “Use your own RabbitMQJob class”.
Ngoài ra bạn có thể custom các queue khác nhau cho việc giao tiếp nếu trong hệ thống của bạn có nhiều hơn 2 service.
Khi đó lệnh gọi của bạn có thể ở dạng như sau
PingJob::dispatch()->onQueue('custom');
Chú ý rằng, việc thêm các custom queue cần khai báo với Rabbitmq nhé.
Trong bài viết này đã hướng dẫn cơ bản các bạn sử dụng Rabbitmq để truyền message giữa các service bằng PHP Laravel. Hi vọng với việc này sẽ giúp ích cho các bạn trong quá trình học tập và làm việc của mình. Nếu bạn thấy bài viết hay và hữu ích hãy like và share bài viết để nhiều người biết đến CodeTuTam hơn bạn nhé!
Bình luận: