L
PHP • Literally Web ID

Membangun Asynchronous Event-Driven Architecture dengan PHP, RabbitMQ, dan Supervisor

⏱️ 15 menit baca • 📅 16 Oct 2026 • 👁️ 1 views
Membangun Asynchronous Event-Driven Architecture dengan PHP, RabbitMQ, dan Supervisor

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap pengembangan web modern, aplikasi monolitik berbasis PHP sering kali dihadapkan pada tantangan performa yang pelik ketika harus memproses operasi berat secara synchronous (sinkron). Bayangkan sebuah skenario e-commerce skala menengah dengan traffic ribuan request per detik. Ketika pengguna melakukan checkout, sistem tidak hanya harus mencatat transaksi ke dalam database relasional (seperti MySQL), tetapi juga harus melakukan serangkaian tugas ikutan: mengirimkan email konfirmasi melalui SMTP/Postfix, menghasilkan dokumen PDF untuk invoice, melakukan sinkronisasi data ke sistem manajemen inventaris eksternal via REST API pihak ketiga, serta memicu notifikasi push real-time melalui WebSocket.

Jika seluruh rangkaian proses ini dieksekusi dalam satu siklus request-response HTTP yang sama, eksekusi skrip PHP akan memakan waktu hingga beberapa detik. Hal ini menyebabkan pemblokiran worker PHP-FPM, peningkatan konsumsi memori secara drastis, kehabisan koneksi database (connection pool exhaustion), dan pada akhirnya menghasilkan lonjakan latensi (Time to First Byte yang buruk) serta Gateway Timeout pada web server Nginx.

[!WARNING] Mengeksekusi proses berat seperti pengiriman email eksternal atau pemrosesan gambar secara sinkron di dalam siklus HTTP PHP adalah pelanggaran arsitektur fatal yang akan melumpuhkan aplikasi Anda di bawah beban produksi yang tinggi.

Kondisi Sebelum vs Sesudah Penerapan

  • Sebelum (Pendekatan Synchronous):
    • Request HTTP diterima $ ightarrow$ Query Database $ ightarrow$ Kirim Email via API/SMTP $ ightarrow$ Generate PDF $ ightarrow$ Render Halaman HTML ke Pengguna.
    • Waktu Eksekusi Total: 3500ms - 8000ms.
    • Kapasitas Konkurensi: Sangat rendah. Server cepat kehabisan worker PHP-FPM.
  • Sesudah (Pendekatan Asynchronous Event-Driven dengan RabbitMQ & Supervisor):
    • Request HTTP diterima $ ightarrow$ Simpan data utama $ ightarrow$ Publikasikan Event/Message ke RabbitMQ $ ightarrow$ Berikan respons instan ke pengguna.
    • Waktu Eksekusi HTTP: 80ms - 150ms.
    • Proses berat ditangani secara asynchronous di latar belakang oleh daemon worker yang dikelola oleh Supervisor.

2. Konsep Inti & Deep Dive Arsitektur

Arsitektur Event-Driven berbasis Message Broker memisahkan produsen pesan (producer) dari konsumen pesan (consumer). Dalam konteks ekosistem web kita, aplikasi web PHP berperan sebagai producer yang bertugas mengirimkan pesan (event payload) ke RabbitMQ. Di sisi lain, sekelompok worker PHP CLI berjalan secara kontinu di latar belakang, mendengarkan antrean (queue), dan memproses setiap pesan yang masuk secara independen.

Alur Data dan Eksekusi (Under the Hood)

+-------------+      HTTP POST      +-------------+
| Web Browser | ------------------> | Nginx/FPM   |
+-------------+                     +-------------+
                                           |
                                    Publish Event
                                           v
+-------------+                     +-------------+
| Supervisor  | <--- Daemonized --- |  RabbitMQ   |
+-------------+                     +-------------+
       |                                   |
    Manages                                | Consume
       v                                   v
+-------------------------------------------------+
| PHP CLI Background Workers (Consumer Processes) |
+-------------------------------------------------+
  1. Publisher (Aplikasi Web): Ketika aksi terjadi (misal: pendaftaran akun), skrip PHP membuat payload JSON, lalu mempublikasikannya ke Exchange di RabbitMQ dengan Routing Key tertentu.
  2. Exchange & Binding: RabbitMQ menerima pesan dan mendistribusikannya ke Queue yang sesuai berdasarkan aturan Exchange (biasanya bertipe Direct atau Topic).
  3. Broker Queue: Pesan disimpan secara aman di dalam memori atau disk oleh RabbitMQ sampai diambil oleh worker.
  4. Consumer (Worker PHP CLI): Skrip PHP yang berjalan abadi (daemon) mengambil pesan dari queue, mengeksekusi logika bisnis berat, lalu mengirimkan sinyal Acknowledgment (ack) kembali ke RabbitMQ untuk menandakan bahwa pesan telah sukses diproses.
  5. Supervisor: Utilitas sistem operasi Linux yang memantau proses worker PHP. Jika worker mengalami crash, kehabisan memori (Memory Leak), atau dihentikan secara paksa, Supervisor akan otomatis melakukan restart instan.

3. Perbandingan Komparatif / Analisis Pendekatan

Untuk memahami mengapa kombinasi RabbitMQ, Supervisor, dan PHP CLI adalah standar industri yang tangguh, mari kita bandingkan dengan pendekatan alternatif lain:

Parameter Komparasi RabbitMQ + Supervisor + PHP Database Queue (MySQL Table) Redis BullMQ (Node/PHP Wrapper)
Latensi Pengiriman Sangat Rendah (< 5ms) Tinggi (I/O Disk Bottleneck) Sangat Rendah (< 2ms)
Keandalan & Durabilitas Tinggi (Pesan persisten di disk/RAM) Sedang (Tergantung transaksi DB) Tinggi (Jika dikonfigurasi AOF/RDB)
Kompleksitas Setup Menengah (Butuh Erlang & Daemon) Sangat Rendah (Pakai DB existing) Menengah-Tinggi
Fitur Routing & DLQ Sangat Kaya (Topic, Headers, DLX) Minimalis / Manual Baik (tergantung implementasi)
Penggunaan Sumber Daya Efisien (RAM terkelola, Erlang VM) Membebani DB utama (Locks/Deadlock) Sangat Efisien di RAM

[!TIP] Hindari menggunakan tabel database relasional sebagai message queue pada aplikasi bervolume tinggi. Operasi SELECT ... FOR UPDATE secara terus-menerus akan memicu table locking dan memperlambat seluruh query operasional aplikasi utama Anda.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum menyelami implementasi kode, pastikan infrastruktur server Anda (biasanya Ubuntu Server 22.04 LTS / 24.04 LTS dengan Nginx dan PHP-FPM) memenuhi spesifikasi berikut:

  • OS: Ubuntu 22.04 / 24.04 LTS
  • Web Stack: Nginx, PHP 8.2+ (CLI & FPM), Composer
  • Message Broker: RabbitMQ Server (memerlukan Erlang runtime)
  • Process Control: Supervisor
  • Database/Cache: MySQL 8.0 / MariaDB 10.11+, Redis

Instalasi Paket Utama via Terminal

Jalankan perintah berikut untuk menginstal RabbitMQ dan Supervisor pada server produksi Anda:

# Update repository sistem
sudo apt update && sudo apt upgrade -y

# Install Supervisor
sudo apt install supervisor -y

# Install Erlang dan RabbitMQ
sudo apt install curl gnupg apt-transport-https -y
curl -1sLf "https://github.com/rabbitmq/signing-keys/releases/download/3.0/rabbitmq-release-signing-key.asc" | sudo gpg --dearmor | sudo tee /usr/share/keyrings/rabbitmq.gpg > /dev/null

# Tambahkan repository RabbitMQ
echo "deb [signed-by=/usr/share/keyrings/rabbitmq.gpg] https://ppa1.rabbitmq.com/rabbitmq/rabbitmq-erlang/deb/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/erlang.list
echo "deb [signed-by=/usr/share/keyrings/rabbitmq.gpg] https://ppa1.rabbitmq.com/rabbitmq/rabbitmq-server/deb/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/rabbitmq.list

sudo apt update
sudo apt install erlang-base erlang-asn1 erlang-crypto erlang-eldap erlang-inets erlang-mnesia erlang-os-mon erlang-public-key erlang-runtime-tools erlang-sasl erlang-ssl erlang-syntax-tools erlang-xmerl -y
sudo apt install rabbitmq-server -y

# Aktifkan dan jalankan layanan
sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-server
sudo systemctl enable supervisor
sudo systemctl start supervisor

Aktifkan juga manajemen web RabbitMQ untuk memudahkan monitoring antrean:

sudo rabbitmq-plugins enable rabbitmq_management
# Buat akun admin baru untuk RabbitMQ
sudo rabbitmqctl add_user admin SuperSecurePassword123
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

5. Panduan Implementasi Langkah demi Langkah

Kita akan membangun implementasi menggunakan pustaka resmi php-amqplib/php-amqplib di dalam proyek PHP berbasis Composer.

Langkah 1: Instalasi Library PHP AMQP

Masuk ke direktori root proyek web Anda (misal: /var/www/html/my-project) dan instal pustaka yang diperlukan:

cd /var/www/html/my-project
composer require php-amqplib/php-amqplib

Langkah 2: Membuat Script Producer (publish.php)

Script ini mensimulasikan endpoint web yang menerima data dari pengguna dan memasukkannya ke dalam antrean RabbitMQ.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use PhpAmqpLibConnectionAMQPStreamConnection;
use PhpAmqpLibMessageAMQPMessage;

try {
    // Hubungkan ke RabbitMQ server
    $connection = new AMQPStreamConnection('localhost', 5672, 'admin', 'SuperSecurePassword123');
    $channel = $connection->channel();

    // Deklarasikan queue yang bersifat persistent (durable)
    $channel->queue_declare('email_queue', false, true, false, false);

    // Data payload yang akan diproses di background
    $data = [
        'to' => 'customer@example.com',
        'subject' => 'Konfirmasi Pesanan #INV-2023-9988',
        'body' => 'Terima kasih telah berbelanja. Pesanan Anda sedang diproses.',
        'timestamp' => time()
    ];

    $msgBody = json_encode($data);

    // Buat pesan dengan mode pengiriman persistent (delivery_mode = 2)
    $msg = new AMQPMessage(
        $msgBody,
        ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
    );

    // Publikasikan pesan ke default exchange dengan routing key 'email_queue'
    $channel->basic_publish($msg, '', 'email_queue');

    echo "[x] Pesan berhasil dikirim ke antrean: " . $msgBody . "\n";

    $channel->close();
    $connection->close();
} catch (Exception $e) {
    error_log("Gagal mempublikasikan pesan ke RabbitMQ: " . $e->getMessage());
    echo "[-] Error: " . $e->getMessage() . "\n";
}

Langkah 3: Membuat Script Consumer / Worker (worker.php)

Script ini berjalan secara kontinu di background, mendengarkan email_queue, dan memproses pesan.

<?php

require_once __DIR__ . '/vendor/autoload.php';

use PhpAmqpLibConnectionAMQPStreamConnection;

// Pastikan script berjalan tanpa batas waktu eksekusi
set_time_limit(0);
ini_set('memory_limit', '256M');

$connection = new AMQPStreamConnection('localhost', 5672, 'admin', 'SuperSecurePassword123');
$channel = $connection->channel();

// Deklarasikan queue yang sama
$channel->queue_declare('email_queue', false, true, false, false);

// Batasi prefetch count agar worker hanya mengambil 1 pesan dalam satu waktu
$channel->basic_qos(null, 1, null);

$callback = function ($msg) {
    echo "[x] Menerima pesan: " . $msg->body . "\n";
    
    $payload = json_decode($msg->body, true);
    
    try {
        // Simulasi proses berat (misal: koneksi SMTP eksternal)
        echo "[i] Mengirim email ke {$payload['to']}...\n";
        sleep(3); // Simulasi latensi jaringan
        
        // Jika berhasil, kirim acknowledgment (ack) ke RabbitMQ
        $msg->ack();
        echo "[✔] Pesan sukses diproses dan di-ack.\n";
        
    } catch (Exception $e) {
        echo "[✖] Gagal memproses pesan: " . $e->getMessage() . "\n";
        
        // Jika gagal, tolak pesan dan masukkan kembali ke antrean (requeue = true) atau kirim ke DLQ
        // Parameter true berarti pesan dikembalikan ke antrean untuk dicoba lagi
        $msg->nack(false, true);
    }
};

// Daftarkan consumer
$channel->basic_consume(
    'email_queue',
    'php_email_worker_1',
    false,
    false, // manual ack diaktifkan
    false,
    false,
    $callback
);

echo "[*] Menunggu pesan masuk. Untuk keluar tekan CTRL+C\n";

while ($channel->is_open()) {
    $channel->wait();
}

$channel->close();
$connection->close();

Langkah 4: Konfigurasi Supervisor untuk Daemonizing Worker

Agar worker.php tetap hidup dan otomatis berjalan saat server reboot atau mengalami crash, kita harus mendaftarkannya ke Supervisor.

Buat file konfigurasi baru di /etc/supervisor/conf.d/php-rabbitmq-worker.conf:

[program:php-rabbitmq-worker]
command=php /var/www/html/my-project/worker.php
process_name=%(program_name)s_%(process_num)02d
numprocs=4
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/php-rabbitmq-worker.log
stopwaitsecs=3600

[!IMPORTANT] Perhatikan parameter numprocs=4. Ini menginstruksikan Supervisor untuk menjalankan 4 instance independen dari worker.php secara bersamaan, memaksimalkan konkurensi pemrosesan pesan di server multi-core.

Setelah file konfigurasi dibuat, muat ulang Supervisor:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start php-rabbitmq-worker:*

Periksa status worker melalui terminal:

sudo supervisorctl status

6. Bedah Kode & Penjelasan Detail Baris Kunci

Mari kita bedah beberapa baris kode paling krusial dari implementasi di atas:

  1. 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT (pada Producer):
    • Fungsi: Menandai pesan agar disimpan ke disk non-volatile RabbitMQ. Jika server RabbitMQ mengalami restart mendadak atau crash, pesan tidak akan hilang.
  2. $channel->basic_qos(null, 1, null); (pada Consumer):
    • Fungsi: Menerapkan Fair Dispatch (Fair QoS). Parameter prefetch_count = 1 memastikan RabbitMQ tidak membanjiri satu worker dengan banyak pesan sekaligus. Worker hanya akan dikirimi pesan baru setelah worker tersebut mengirimkan sinyal ack pada pesan sebelumnya.
  3. $msg->ack(); vs $msg->nack(false, true);:
    • Fungsi: Mekanisme Manual Acknowledgment. Ini adalah pilar utama keandalan (reliability). Jika skrip PHP mati di tengah jalan sebelum mencapai $msg->ack() (misalnya karena Segmentation Fault atau kehabisan memori), RabbitMQ tahu bahwa pesan tersebut belum selesai diproses dan akan otomatis mengembalikannya ke antrean untuk dikonsumsi worker lain.

7. Skenario Produksi & Studi Kasus Dunia Nyata

Pada arsitektur produksi berskala besar (misal: platform flash sale), lonjakan data yang masuk dapat mencapai 10.000 transaksi per detik. Jika worker PHP Anda melakukan koneksi baru ke database pada setiap pesan yang diterima, MySQL akan langsung mengalami Too many connections.

Strategi Optimasi Produksi:

  • Persistent Database Connection: Gunakan koneksi database persisten atau Connection Pooling (seperti ProxySQL atau Swoole/RoadRunner jika menggunakan runtime alternatif) di dalam worker PHP agar tidak membuka/menutup koneksi TCP ke MySQL secara berulang-ulang.
  • Batching / Bulk Insert: Jika worker bertugas memasukkan log atau data analitik, kumpulkan pesan ke dalam memori array hingga mencapai jumlah tertentu (misal: 100 pesan) sebelum melakukan bulk insert satu kali ke database.

8. Common Pitfalls, Anti-Patterns & Solusi

  • Anti-Pattern 1: Mengabaikan Memory Leak pada Long-Running Process.
    • Masalah: PHP secara historis dirancang untuk skrip berumur pendek (shared-nothing architecture). Ketika skrip PHP dijalankan terus-menerus sebagai daemon, akumulasi memori dari objek yang tidak dibebaskan akan memicu Memory Leak.
    • Solusi: Batasi jumlah pesan yang diproses oleh satu worker sebelum melakukan restart mandiri, atau gunakan fungsi gc_collect_cycles() secara berkala. Di Supervisor, Anda juga bisa mengatur maxbytes dan parameter restart otomatis.
  • Anti-Pattern 2: Menggunakan auto_ack = true (Automatic Acknowledgment).
    • Masalah: Jika worker crash tepat setelah menerima pesan tetapi sebelum selesai memprosesnya, pesan tersebut hilang selamanya karena RabbitMQ menganggapnya sudah sukses.
    • Solusi: Selalu gunakan auto_ack = false dan panggil ack() secara manual setelah eksekusi bisnis benar-benar tuntas.

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk menguji ketahanan sistem asynchronous yang telah dibangun, Anda dapat menggunakan alat load testing seperti k6 atau wrk untuk menghantam endpoint publish.php, sementara Anda memantau antrean melalui RabbitMQ Management Dashboard (http://localhost:15672).

Parameter Tuning Linux Kernel (/etc/sysctl.conf)

Untuk menangani puluhan ribu koneksi TCP dan file descriptor yang dibuka oleh RabbitMQ dan Nginx, tambahkan konfigurasi berikut:

fs.file-max = 2097152
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.somaxconn = 65535

Terapkan perubahan dengan perintah:

sudo sysctl -p

10. Checklist Penerapan Produksi & Kesimpulan

Gunakan checklist berikut sebelum merilis arsitektur Event-Driven ini ke lingkungan produksi:

  • RabbitMQ dilindungi dengan kredensial yang kuat (bukan guest/guest) dan port manajemen dibatasi via firewall (UFW).
  • Semua script worker PHP menggunakan auto_ack = false dengan error handling yang komprehensif.
  • Supervisor dikonfigurasi dengan numprocs yang disesuaikan dengan jumlah vCPU server.
  • Log rotasi untuk Supervisor telah diaktifkan agar tidak menghabiskan kapasitas disk server.
  • Monitoring kesehatan antrean (Queue Length) terintegrasi dengan sistem alert (Prometheus/Grafana/PagerDuty).

Dengan memindahkan proses berat dari sinkronus web request ke asynchronous worker menggunakan RabbitMQ dan Supervisor, aplikasi PHP Anda kini memiliki skalabilitas, ketahanan, dan performa tingkat enterprise yang siap menghadapi lonjakan traffic ekstrem.

🏷️ Tags: #Nginx #High Concurrency #Performance #Sysadmin #Web Server