lukaszstaniszewski.pl

blog programistyczny

lukaszstaniszewski.pl

blog programistyczny

RabbitMQ w PHP – wprowadzenie

W tym artykule opiszę Ci, czym są asynchroniczne operacje oraz jak za pomocą RabbitMQ i PHP, wykonywać takie operacje. Gdy dowiesz się czym jest asynchroniczność, będziesz mógł zrozumieć jak działają pewne funkcjonalności w portalach społecznościowych takich jak facebook, linkedin itd. Ciekawe co nie? 🙂

Całość kodu, który pokazałem w tym wpisie możesz znaleźć tutaj.

 

Czym są asynchroniczne operacje?

By zrozumieć czym są asynchroniczne operacje, na początku musisz zrozumieć czym są synchroniczne operacje.

Jeżeli programujesz np. w PHP, to wiesz że wywoływany kod jest interpretowany w czasie rzeczywistym. I to jest dokładnie cała synchroniczność, operacje które są wykonywane tu i teraz.

Więc teraz przejdźmy do asynchronicznych operacji. Jest to wykonywanie operacji np. 10 minut po wywołaniu żądania. W skrócie coś co wykona się za jakiś czas, nie w momencie wywołania.

Prostym przykładem może być, wysyłanie e-maila potwierdzającego utworzenie konta. Czy ważne jest aby e-mail dotarł do użytkownika w momencie wysłania żądania utworzenia konta? Otóż nie, e-mail równie dobrze, może dotrzeć do skrzynki użytkownika za 15 minut. Właśnie, dzięki operacjom asynchronicznym, możemy otrzymać taki efekt.

 

Ale co dzięki temu zyskujemy?

Wyobraźmy sobie sytuacje gdy nasz system musi zapisać w ciągu 1 godziny 10 mln powiadomień do użytkowników, a po godzinie tylko 100 tysięcy. Patrząc pod względem optymalizacyjnym widzimy beznadziejna sytuację. I teraz nasuwa się jedno pytanie, co możemy zrobić z taką funkcjonalnością?

Nie wiedząc co to jest asynchroniczność, widzimy tylko jedno rozwiązanie, a dokładniej jeszcze więcej serwerów. Jeżeli tak myślałeś, to muszę Cię zawieść, to błędny tok myślenia ponieważ w danym czasie lub przez większość czasu, pewna ilość serwerów nic nie będzie robić.

W takich właśnie miejscach, jak w naszym przykładzie, asynchroniczność pokazuję swoją przydatność. Stosując system kolejkowy (a dokładniej broker wiadomości tj. RabbitMQ, a co za tym idzie asynchroniczność), możemy rozłożyć zapis powiadomień na pewną ilość konsumentów (inaczej kod który obsługuje dane, wysłane za pomocą kolejki) które obsługują daną kolejkę, w tym przypadku kolejkę powiadomień. Dzięki temu zyskujemy oszczędności.

Drobne wtrącenie poza tematykę wpisu, do takiego przykładu jak wyżej jeszcze lepiej może nadać się serverless.

Oczywiście, nie może być tak kolorowo i to rozwiązanie musi mieć swoje wady. Bazując na naszym prostym przykładzie, możemy wywnioskować jedną główną wadę. Użytkownik już nie dostanie powiadomienia od razu (synchronicznie), może się to stać za 5 minut ale również i za 1 dzień.

W wolnej chwili zwróć uwagę, jak działają powiadomienia na linkedin lub facebook’u a zobaczysz coś ciekawego, o czym wspominałem na początku artykułu. 🙂

Podsumowując, wszystkie zapisy powiadomień rozłożą się w czasie, co da oszczędności zasobów serwera.

 

Kilka sytuacji kiedy warto stosować asynchroniczność

Jest to tylko oczywiście moje zdanie i jak każdy człowiek mogę się pomylić, dlatego zapraszam do dyskusji w komentarzach. Każda uwaga/krytyka mile widziana. 🙂

  1. Ruch jest różny (w takich sytuacjach również, bardzo dobrze się sprawuję faas (function as a service a dokładniej serverless))
  2. Duży oraz stały lub z małymi wahaniami ruch dla danej funkcjonalności
  3. Mikroserwisy –  a dokładniej komunikacja między serwisem a gateway oraz w zależności od wyboru typu architektury komunikacji, między serwisami
  4. Chęć zmniejszenia używanych zasobów przez daną funkcjonalność
  5. Gdy wykonujemy jakąś logikę biznesową, która może zostać wykonana po żądaniu użytkownika np. dobranie proponowanych reklam
  6. Funkcjonalności których wykonywanie trwa bardzo długo (w zależności od sytuacji, czasem lepszym wyborem okazuje się CRON)
  7. Funkcjonalności dla których czas wykonania, nie jest istotny a do tego potrzebujemy minimalizacji zużywanych zasobów serwera/serwerów.

Jest to tylko kilka sytuacji w których asynchroniczność bardzo dobrze się sprawdza. Na liście, mogłyby się również znaleźć wywołania asynchroniczne np. w RxJS (w PHP RxPHP) ale ten artykuł jest głównie nastawiony na użycie asynchronicznych operacji wraz z RabbitMQ i PHP dlatego pominąłem przypadki w innych językach itp.

 

Czym jest RabbitMQ?

RabbitMQ jest brokerem wiadomości (ang. message broker). W skrócie, zarządza on naszymi kolejkami czyli operacjami asynchronicznymi. Najprościej wytłumaczę Ci to na prostej grafice, która prezentuję zasadę działania RabbitMQ.

Flow działania RabbitMQ

Początkowo nasza wiadomość trafia z producenta do exchange. Exchange, odpowiada za przydzielenie danej wiadomości do odpowiedniej kolejki. Nasz konsument, dzięki połączeniu z RabbitMQ (w tym artykule za pomocą protokołu AMQP), odbiera dane z kolejki i wykonuje swoje zadanie.

 

Jak działa kolejka?

Wyobraźmy sobie sytuację z życia, a dokładniej kolejkę w sklepie. Każdy klient, stojący w kolejce posiada produkty które chce kupić. Tą sytuację, która spotyka każdego z nas, przełóżmy na RabbitMQ i jego system kolejkowania.

W kolejce znajdują się dane, które po kolei tak jak były wysyłane, będą trafiać do konsumenta np. user consumer. Konsument wykona swoje zadanie i znów pobierze kolejne dane do wykonania swojej funkcjonalności. W przypadku gdy kolejka jest pusta konsument oczekuje na kolejne dane i nie wykonuje swojej logiki biznesowej.

Stwórzmy prostą asynchroniczną operację

W artykule nie będę pokazywał testów jednostkowych, kontrolerów itp. jeżeli chcesz sobie zobaczyć ten kod kliknij tutaj.

Zacznijmy standardowo od composer.json

{
  "type": "project",
  "require": {
    "php": "^7.3",
    "ext-json": "*",
    "ext-xml": "*",
    "ext-iconv": "*",
    "ext-sockets": "*",
    "doctrine/annotations": "1.6.*",
    "doctrine/dbal": "2.9.*",
    "doctrine/doctrine-bundle": "1.11.*",
    "doctrine/orm": "2.6.*",
    "symfony/asset": "4.*",
    "symfony/console": "4.*",
    "symfony/form": "4.*",
    "symfony/framework-bundle": "4.*",
    "symfony/twig-bundle": "4.*",
    "symfony/validator": "4.*",
    "symfony/yaml": "4.*",
    "symfony/dotenv": "4.*",
    "symfony/flex": "1.2.*",
    "sensio/framework-extra-bundle": "^5.1",
    "twig/twig": "2.*",
    "php-amqplib/php-amqplib": "2.9.*"
  },
  "require-dev": {
    "phpunit/phpunit": "8.0.*",
    "mockery/mockery": "1.2.*",
    "phpmd/phpmd" : "2.6.*",
    "phpstan/phpstan": "0.11.*"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "App\\Tests\\": "tests/"
    }
  },
  "config": {
    "preferred-install": {
      "*": "dist"
    },
    "sort-packages": true
  },
  "scripts": {
    "auto-scripts": {
      "cache:clear": "symfony-cmd",
      "assets:install %PUBLIC_DIR%": "symfony-cmd"
    },
    "post-install-cmd": [
      "@auto-scripts"
    ],
    "post-update-cmd": [
      "@auto-scripts"
    ]
  },
  "extra": {
    "symfony": {
      "allow-contrib": false,
      "require": "4.3.*"
    }
  }
}

 

By móc komunikować się z RabbitMQ musimy użyć paczki php-amqplib/php-amqplib (patrz composer.json). Komunikacja z RabbitMQ opiera się o protokół AMQP. Musimy zwrócić uwagę na to, że jest to protokół bardzo kosztowny w połączeniu. Z tego powodu, zawsze warto wysłać możliwie jak najwięcej wiadomości do RabbitMQ, za pomocą jednego połączenia.

Przyszedł czas na naszą prostą aplikację webową. Aplikacja ma za zadanie, przyjąć dane (interfejs webowy), wysłać wiadomości do RabbitMQ (wtedy aplikacja staje się producentem) oraz wyświetlić wszystkie powiadomienia.

Services.yaml (część konfiguracji)

    PhpAmqpLib\Connection\AMQPStreamConnection:
        class: PhpAmqpLib\Connection\AMQPStreamConnection
        arguments:
            - '%rabbitmq.host%'
            - '%rabbitmq.port%'
            - '%rabbitmq.user%'
            - '%rabbitmq.pass%'

    SplObjectStorage:
        class: '\SplObjectStorage'
        public: true
        autowire: true

 

<?php
declare(strict_types=1);

namespace App\Service;

use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection;

/**
 * Class RabbitMQConnectService
 * @package App\Service
 */
class RabbitMQConnectService
{
    /**
     * @var AMQPStreamConnection
     */
    private $connection;

    /**
     * RabbitMQConnectService constructor.
     * @param AMQPStreamConnection $connection
     */
    public function __construct(AMQPStreamConnection $connection)
    {
        $this->connection = $connection;
    }

    /**
     * @return AMQPChannel
     */
    public function getChannel(): AMQPChannel
    {
        if (!$this->connection->isConnected()) {
            $this->connection->reconnect();
        }

        return $this->connection->channel();
    }

    /**
     * @param AMQPChannel $AMQPChannel
     * @throws \Exception
     */
    public function closeConnection(AMQPChannel $AMQPChannel): void
    {
        $AMQPChannel->close();
        $this->connection->close();
    }
}

 

<?php
declare(strict_types=1);

namespace App\Composite\RabbitMQ;

use App\Service\RabbitMQConnectService;
use PhpAmqpLib\Channel\AMQPChannel;
use \SplObjectStorage;

/**
 * Class RabbitMQCompositeAbstract
 * @package App\Composite\RabbitMQ
 */
abstract class RabbitMQCompositeAbstract extends RabbitMQComponentAbstract
{
    /**
     * @var AMQPChannel
     */
    protected $AMQPChannel;
    
    /**
     * @var SplObjectStorage
     */
    protected $components;
    
    /**
     * @var RabbitMQConnectService
     */
    private $rabbitMQConnectService;
    
    /**
     * RabbitMQCompositeAbstract constructor.
     * @param RabbitMQConnectService $rabbitMQConnectService
     * @param SplObjectStorage $splObjectStorage
     */
    public function __construct(RabbitMQConnectService $rabbitMQConnectService, SplObjectStorage $splObjectStorage)
    {
        $this->rabbitMQConnectService = $rabbitMQConnectService;
        $this->AMQPChannel = $this->rabbitMQConnectService->getChannel();
        $this->components = $splObjectStorage;
    }
    
    /**
     * @return bool
     */
    public function isComposite(): bool
    {
        return true;
    }
    
    /**
     * @param AMQPChannel $AMQPChannel
     */
    public function run(AMQPChannel $AMQPChannel): void
    {
        // Do nothing
    }
    
    /**
     *
     */
    abstract public function runComposite(): void;
    
    /**
     * @param RabbitMQComponentAbstract $rabbitMQComponentAbstract
     * @return RabbitMQCompositeAbstract
     */
    abstract public function add(RabbitMQComponentAbstract $rabbitMQComponentAbstract): RabbitMQCompositeAbstract;
    
    /**
     * @param RabbitMQComponentAbstract $rabbitMQComponentAbstract
     * @return RabbitMQCompositeAbstract
     */
    abstract public function remove(RabbitMQComponentAbstract $rabbitMQComponentAbstract): RabbitMQCompositeAbstract;
    
    /**
     * @throws \Exception
     */
    protected function closeConnection(): void
    {
        $this->rabbitMQConnectService->closeConnection($this->AMQPChannel);
    }
}

 

<?php
declare(strict_types=1);

namespace App\Composite\RabbitMQ;

use App\Exception\CompositeStorageIsEmptyException;

/**
 * Class NotificationComposite
 * @package App\Composite\RabbitMQ
 */
class NotificationComposite extends RabbitMQCompositeAbstract
{
    /**
     * @throws CompositeStorageIsEmptyException
     */
    public function runComposite(): void
    {
        if ($this->components->count() <= 0) {
            throw new CompositeStorageIsEmptyException('Storage is empty in: ' . \get_class($this));
        }

        foreach ($this->components as $component) {
            $component->run($this->AMQPChannel);
        }

        $this->closeConnection();
    }

    /**
     * @param RabbitMQComponentAbstract $rabbitMQComponentAbstract
     * @return RabbitMQCompositeAbstract
     */
    public function add(RabbitMQComponentAbstract $rabbitMQComponentAbstract): RabbitMQCompositeAbstract
    {
        $this->components->attach($rabbitMQComponentAbstract);
        return $this;
    }

    /**
     * @param RabbitMQComponentAbstract $rabbitMQComponentAbstract
     * @return RabbitMQCompositeAbstract
     */
    public function remove(RabbitMQComponentAbstract $rabbitMQComponentAbstract): RabbitMQCompositeAbstract
    {
        $this->components->detach($rabbitMQComponentAbstract);
        return $this;
    }
}

 

<?php
declare(strict_types=1);

namespace App\Composite\RabbitMQ;

use PhpAmqpLib\Channel\AMQPChannel;

/**
 * Class RabbitMQComponentAbstract
 * @package App\Composite\RabbitMQ
 */
abstract class RabbitMQComponentAbstract
{
    /**
     * @param AMQPChannel $AMQPChannel
     */
    abstract public function run(AMQPChannel $AMQPChannel): void;
}

 

<?php
declare(strict_types=1);

namespace App\Composite\RabbitMQ\Component;

use App\Composite\RabbitMQ\RabbitMQComponentAbstract;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;

/**
 * Class AlertNotificationComponent
 * @package App\Composite\RabbitMQ\Component
 */
class AlertNotificationComponent extends RabbitMQComponentAbstract
{
    /**
     * @var string
     */
    private $message;

    /**
     * AlertNotificationComponent constructor.
     * @param string $message
     */
    public function __construct(string $message)
    {
        $this->message = $message;
    }

    /**
     * @param AMQPChannel $AMQPChannel
     */
    public function run(AMQPChannel $AMQPChannel): void
    {
        $message = new AMQPMessage(
            \json_encode([
                'message' => $this->message,
                'type' => 'alert'
            ]),
            ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
        );

        $AMQPChannel->basic_publish($message, '', 'alert_notification');
    }
}

 

<?php
declare(strict_types=1);

namespace App\Composite\RabbitMQ\Component;

use App\Composite\RabbitMQ\RabbitMQComponentAbstract;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;

/**
 * Class NewMessageNotificationComponent
 * @package App\Composite\RabbitMQ\Component
 */
class NewMessageNotificationComponent extends RabbitMQComponentAbstract
{
    /**
     * @var string
     */
    private $message;

    /**
     * NewMessageNotificationComponent constructor.
     * @param string $message
     */
    public function __construct(string $message)
    {
        $this->message = $message;
    }

    /**
     * @param AMQPChannel $AMQPChannel
     */
    public function run(AMQPChannel $AMQPChannel): void
    {
        $message = new AMQPMessage(
            \json_encode([
                'message' => $this->message,
                'type' => 'new_message'
            ])
        );

        $AMQPChannel->basic_publish($message, '', 'new_message_notification');
    }
}

 

Tworzymy odbiorcę kolejki (Consument/Worker)

<?php
declare(strict_types=1);

namespace App\Service;

use App\Exception\InvalidArgsException;
use App\Factory\NotificationFactory;
use App\Repository\NotificationRepository;
use PhpAmqpLib\Message\AMQPMessage;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * Class SaveNotificationService
 * @package App\Service
 */
class SaveNotificationService
{
    /**
     * @var NotificationRepository
     */
    private $notificationRepository;

    /**
     * @var NotificationFactory
     */
    private $notificationFactory;

    /**
     * @var ValidatorInterface
     */
    private $validator;

    /**
     * SaveNotificationService constructor.
     * @param NotificationRepository $notificationRepository
     * @param NotificationFactory $notificationFactory
     * @param ValidatorInterface $validator
     */
    public function __construct(
        NotificationRepository $notificationRepository,
        NotificationFactory $notificationFactory,
        ValidatorInterface $validator
    ) {
        $this->notificationRepository = $notificationRepository;
        $this->notificationFactory = $notificationFactory;
        $this->validator = $validator;
    }

    /**
     * @param AMQPMessage $jsonNotification
     * @throws InvalidArgsException
     * @throws \App\Exception\NotFullDataInFactoryException
     * @throws \Doctrine\ORM\ORMException
     * @throws \Doctrine\ORM\OptimisticLockException
     */
    public function save(AMQPMessage $jsonNotification): void
    {
        $arrayNotification = \json_decode($jsonNotification->body, true);

        $notification = $this->notificationFactory->buildNotification($arrayNotification);

        if (\count($this->validator->validate($notification)) > 0) {
            throw new InvalidArgsException('Invalid arguments in notification entity');
        }

        $this->notificationRepository->save($notification);
    }
}
<?php
declare(strict_types=1);

namespace App\Console\RabbitMQ;

use App\Exception\InvalidArgsException;
use App\Service\RabbitMQConnectService;
use App\Service\SaveNotificationService;
use App\Service\Validator\TypeNotificationValidator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class AlertNotificationWorkerConsole
 * @package App\Console\RabbitMQ
 */
class NotificationWorkerConsole extends Command
{
    /**
     * @var \PhpAmqpLib\Channel\AMQPChannel
     */
    private $AMQPChannel;

    /**
     * @var SaveNotificationService
     */
    private $saveNotificationService;

    /**
     * NotificationWorkerConsole constructor.
     * @param RabbitMQConnectService $rabbitMQConnectService
     * @param SaveNotificationService $saveNotificationService
     */
    public function __construct(
        RabbitMQConnectService $rabbitMQConnectService,
        SaveNotificationService $saveNotificationService
    ) {
        $this->AMQPChannel = $rabbitMQConnectService->getChannel();
        $this->saveNotificationService = $saveNotificationService;
        parent::__construct();
    }

    /**
     * @codeCoverageIgnore
     */
    public function configure(): void
    {
        $this->setName('rabbitMQ:notification');
        $this->addArgument('type', InputArgument::REQUIRED, 'alert or new_message');
        $this->addArgument('durable', InputArgument::REQUIRED, 'alert or new_message');
    }

    /**
     * @param InputInterface $input
     * @param OutputInterface $output
     * @throws InvalidArgsException
     */
    public function execute(InputInterface $input, OutputInterface $output): void
    {
        $type = $input->getArgument('type');
        if (!TypeNotificationValidator::validate($type)) {
            throw new InvalidArgsException('Undefined type');
        }

        $output->writeln(ucfirst($type) . ' notification worker ready to working');
        $durable = $this->getDurable($input->getArgument('durable'));
        $this->consumeChannel($type, $durable);

        while (\count($this->AMQPChannel->callbacks)) {
            try {
                $this->AMQPChannel->wait();
                $output->writeln('I\'m added notification to database!');
            } catch (\Exception $exception) {
                $output->writeln('Exception message: ' . $exception->getMessage());
            }
        }
    }

    /**
     * @param string $durableArgs
     * @return bool
     * @throws InvalidArgsException
     */
    private function getDurable(string $durableArgs): bool
    {
        if ($durableArgs === 'true') {
            return true;
        } elseif ($durableArgs === 'false') {
            return false;
        }

        throw new InvalidArgsException('Undefined durable');
    }

    /**
     * @param string $type
     * @param bool $durable
     */
    private function consumeChannel(string $type, bool $durable): void
    {
        $this->AMQPChannel->queue_declare(
            $type . '_notification',
            false,
            $durable
        );
        $this->AMQPChannel->basic_consume(
            $type . '_notification',
            '',
            false,
            true,
            false,
            false,
            [$this->saveNotificationService, 'save']
        );
    }
}

Argument trwały (ang: Durable) oznacza że kolejka jest stała i w razie awarii, zatrzymania lub ponownego uruchamiania RabbitMQ, dane w kolejce nie zostaną utracone. Gdy używamy trwałych kolejek, musimy również powiadomić wiadomość (klasa AMQPMessage), że kolejka jest trwała. Robimy to za pomocą opcji w konfiguracji “delivery_mode”.  Ale tutaj również jest pewien haczyk. Kolejka ma swój delay (pl: opóźnienie), przed zapisaniem danych na dysk. Te dane wtedy są przetrzymywane w pamięci podręcznej i w razie awarii zostaną utracone. W takiej sytuacji, jedynym rozwiązaniem jest potwierdzenie zapisu przesłanych danych na dysk, ale tego nie będziemy tutaj omawiać, może w przyszłym wpisie.  🙂 Warto zwrócić uwagę, czy ten odsetek utraconych danych jest aż tak ważny, że musimy potwierdzać zapis danych na dysku.

Dla porównania druga kolejka nie jest trwała, czyli w razie awarii lub restartu RabbitMQ dane przesłane do kolejki zostaną utracone.

 

Oczywiście jest to tylko kod przykładowy i ma on czasem dziwne rozwiązania.

 

Uruchommy nasz przykład

W tym przykładzie aby zobaczyć efekt naszej pracy, skorzystamy z Docker’a.

version: '3.7'

services:
  app:
    build:
      context: ./etc/php7.3-app-pgsql
      dockerfile: php7.3-app-pgsql.Dockerfile
    image: php7.3-app-pgsql
    volumes:
      - ./app:/var/www/html
    ports:
      - 80:80
    networks:
      - rabittmq
      - database
    container_name: 'app-tutorial'

  rabbitmq:
    image: rabbitmq:3.7.15-management
    environment:
      - RABBITMQ_DEFAULT_USER=admin
      - RABBITMQ_DEFAULT_PASS=administrator
    ports:
      - '15672:15672'
    volumes:
      - rabbitmq:/var/lib/rabbitmq
    networks:
      - rabittmq
    container_name: 'rabbitmq-tutorial'

  database:
    image: postgres:11.2
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=administrator
      - POSTGRES_DB=rabbitMQStart
    volumes:
      - database:/var/lib/postgresql/data
    networks:
      - database
    container_name: 'pgsql-tutorial'


volumes:
  database:
    driver: local
  rabbitmq:
    driver: local

networks:
  database:
  rabittmq:

Mamy kod, przygotowanego docker compose ale przydałoby się uruchomić Docker’a oraz zobaczyć efekty naszej pracy.

docker-compose up -d
docker container exec -it app-tutorial bash

# Konsola kontenera

php bin/console doctrine:schema:update --force

# Uruchamiamy konsumenta dla kolejki alert_notification
php bin/console rabbitMQ:notification alert true

# Otwieramy nowe okno w konsoli (na ubuntu skrót: ctrl + shift + t), można to również zrobić za pomocą screen

php bin/console doctrine:schema:update --force
php bin/console rabbitMQ:notification new_message false

Mamy postawionego docker’a, uruchomionych konsumentów, więc przyszła pora skorzystać z aplikacji webowej (url: localhost). Wystarczy teraz że w pole (textarea) wpiszesz dane podane niżej i zobaczysz efekt pracy.

[
    {"message":"Hello World","type":"alert"},
    {"message":"Hello new message","type":"new_message"},
    {"message":"New alert","type":"alert"},
    {"message":"Next new message","type":"new_message"},
    {"message":"Next alert","type":"alert"}
]

Nasze workery pokazały że dane zostały zapisane do bazy danych?

To bardzo dobrze! Teraz wystarczy że wejdziesz pod url localhost/all i zobaczysz wszystkie dane zapisane w bazie danych. 🙂

Panel webowy RabbitMQ

Jeżeli chcesz zobaczyć stan RabbitMQ z poziomu przeglądarki tj. np. ile workerów jest obecnie połączonych z RabbitMQ czy obecny stan kolejki, wystarczy że wejdziesz pod url: localhost:15672 hasło: administrator, login: admin

Status kolejki w UI RabbitMQ

 

Wykresy w UI RabbitMQ

Podsumujmy cały wpis

Przyczyn dlaczego warto stosować asynchroniczne operacje jest wiele. Wszystko tak naprawdę zależy od danej sytuacji, ale tak to bywa w IT. Musimy zawsze samemu lub wraz z zespołem przeanalizować czy warto wdrażać asynchroniczne operację.

Mam nadzieję że w zakresie, wprowadzenia do RabbitMQ wyczerpałem dokładnie temat i jesteś zadowolony z poświęconego czasu na ten artykuł. 🙂 Jeżeli nie, z chęcią chciałbym poznać powód takiej opinii. Na podstronie kontakt możesz znaleźć do mnie maila i napisać dlaczego Ci się nie podobał wpis lub napisz komentarz, będę Ci bardzo wdzięczny za podzielenie się opinią.

Jeżeli chcesz poszerzyć swoją wiedzę z zakresu RabbitMQ, warto zajrzeć do oficjalnego tutoriala, wystarczy że klikniesz tutaj i zostaniesz przekierowany na stronę tutoriala.

Źródła:

Dzięki i do zobaczenia, Łukasz!

RabbitMQ w PHP – wprowadzenie
Przewiń na górę