RabbitMQ je skvělá věc. Objevil jsem ji poměrně nedávno (já vím ostuda 🤦♂️) – respektive jsem věděl o co přibližně jde, ale neměl jsem potřebu ji použít. Teď jsem ale programoval systém, který instaluje instance PHP aplikace na nové subdomény na vyžádání z jiného webu (serveru). Použít CRON zde nedává moc smysl, protože by běžel většinu času zbytečně a musel by se spouštět velmi často, aby spustil registraci co nejdříve to bude možné. Myslím, že RabbitMQ je i hezčí řešení. Krásně čeká, až se do fronty zařadí alespoň jeden požadavek a potom spustí požadovanou akci jednu po druhé (pokud se jich sejde víc).
Instalace na server
Instaloval jsem ji na VPS s Ubuntu 24.04 a Quick Start Script z RabbitMQ dokumentace fungoval výborně, až na 3 věci, které je potřeba si pohlídat
- Pozor na hostname – případně je potřeba
nastavit v /etc/rabbitmq/rabbitmq-env.conf (ten defaultně neexistuje, takže vytvořit)
a nastavit NODENAME=rabbit@localhost - Nezapomenout povolit porty ve firewallu
– ufw allow 15672- pokud chcete web management rozhraní přes příkaz
rabbitmq-plugins enable rabbitmq_management
– ufw allow 5672 – pokud chcete posílat do fronty z jiného serveru než na kterým je RabbitMQ nainstalovaný přes AMQP - Vytvořit nového uživatele s pořádný heslem a smazat výchozího admin uživatele, který má jméno guest a heslo guest.
rabbitmqctl add_user "jiny_admin" # přidá uživatele a vyžádá si zadání hesla rabbitmqctl set_user_tags jiny_admin administrator # nastaví uživateli "kategorii" administrator rabbitmqctl set_permissions -p / "jiny_admin" ".*" ".*" ".*" # nastaví uživateli práva rabbitmqctl list_users # oveřte si, že je opravdu vše nastavené rabbitmqctl delete_user 'guest' # smaže výhozího uživatele
PHP
Pro PHP má RabbitMQ composer balíček php-amqplib/php-amqplib, který se hodí jak pro vytváření položek do fronty, tak i na jejich vyřízení.
Nový požadavek do fronty
Toto jednoduše přidá do fronty se jménem „nazev_fronty“ nový požadavek v jehož obsahu je v ukázce e-mail.
U metody „queue_declare“ je potřeba si pohrát s parametry podle toho, co potřebujete. V ukázce je nastaveno: odolné vůči restaru serveru (to je jedno „true“).
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('IP adresa serveru', 5672, 'jméno', 'heslo');
$channel = $connection->channel();
$channel->queue_declare('nazev_fronty', false, true, false, false);
$email = 'muj@email.cz';
$msg = new \PhpAmqpLib\Message\AMQPMessage($email);
$channel->basic_publish($msg, '', 'newapp');
$channel->close();
$connection->close();
Vyřízení požadavku
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('IP adresa serveru', 5673, 'jméno', 'heslo');
$channel = $connection->channel();
$channel->queue_declare('nazev_fronty', false, true, false, false);
$channel->basic_qos(null, 1, null); // Vezme 1 položku z fronty
$channel->basic_consume('nazev_fronty', '', false, false, false, false, function (AMQPMessage $message) {
$email = $message->getBody();
// zpracování - já zde třeba spouším shell skript pro instalaci PHP aplikace na novou subdoménu, instaluji SSL cesrtifikát a posílám info email, že vše dopadlo OK
$message->getChannel()->basic_ack($message->getDeliveryTag()); // Označí se jako zpracované
});
// Díky této část se neukončí běh skriptu, ale vyčkává se na nový pořadavek ve frontě
while (count($channel->callbacks)) {
$channel->wait();
}
$channel->close();
$this->connection->close();
PHP jako služba
Aby vyřizovací skript běžel vždy, nainstaluji si ho jako službu do serveru.
Vytvořím soubor /etc/systemd/system/nazev_sluzby.service, který definuje službu a jako příkaz spustí /bin/php /var/www/vyrizovaci_skript.php
[Unit]
Description=Název služby
[Service]
User=root
Type=simple
TimeoutSec=0
PIDFile=/var/run/nazev_sluzby.pid
ExecStart=/bin/php /var/www/vyrizovaci_skript.php
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=default.target
Poté spustím službu a ověřím, že běží a je hotovo :)
systemctl enable nazev_sluzby
systemctl start nazev_sluzby
systemctl status nazev_sluzby
Znáte někoho, komu by článek mohl pomoct? Zasdílejte mu ho :)