Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

AMQP con RabbitMQ

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Abre tu archivo .env y comprueba la configuración MESSENGER_TRANSPORT_DSN. Hemos estado utilizando el tipo de transporte Doctrine. La cadena doctrine://default dice que los mensajes deben almacenarse utilizando la conexión default de Doctrine. Enconfig/packages/messenger.yaml, estamos haciendo referencia a esta variable de entorno para los transportes async y async_priority_high.

Así que... ¡sí! Hemos estado almacenando los mensajes en una tabla de la base de datos. Ha sido rápido de configurar, fácil de usar -porque ya entendemos de bases de datos- y suficientemente robusto para la mayoría de los casos de uso.

Hola AMQP... RabbitMQ

Pero el "sistema de colas" o "corredor de mensajes" estándar de la industria no es una tabla de base de datos, es algo llamado AMQP, o "Protocolo Avanzado de Colas de Mensajes". AMQP no es en sí mismo una tecnología... es un "estándar" de cómo debe funcionar un, así llamado, "sistema de corredor de mensajes". Luego, diferentes sistemas de colas pueden "implementar" este estándar. Sinceramente, normalmente cuando alguien habla de AMQP, se refiere a una herramienta concreta: RabbitMQ.

La idea es la siguiente: de la misma manera que lanzas un "servidor de base de datos" y le haces consultas, puedes lanzar una "instancia de Rabbit MQ" y enviar mensajes a ella y recibir mensajes de ella. A alto nivel... no funciona de forma muy diferente a nuestra simple tabla de base de datos: introduces mensajes... y los solicitas después.

Entonces... ¿cuáles son las ventajas de utilizar RabbitMQ en lugar de Doctrine? Quizás... ¡nada! Lo que quiero decir es que, si sólo utilizas las funciones estándar de Messenger y nunca profundizas en ellas, ambas funcionarán bien. Pero si tienes un sistema muy escalado o quieres utilizar algunas funciones avanzadas y específicas de RabbitMQ, bueno... entonces... ¡RabbitMQ es la respuesta!

¿Cuáles son esas características más avanzadas? Bueno, quédate conmigo en los próximos capítulos y empezarás a descubrirlas.

Lanzar una instancia a través de CloudAMQP.com

La forma más sencilla de poner en marcha una instancia de RabbitMQ es a través de cloudamqp.com: un servicio impresionante para RabbitMQ basado en la nube... ¡con una capa gratuita para que podamos jugar! Después de iniciar sesión, crea una nueva instancia, dale un nombre, selecciona cualquier región... sí, queremos el nivel gratuito y... "Crear instancia".

Configuración del transporte AMQP

¡Genial! Haz clic en la nueva instancia para encontrar... ¡una hermosa cadena de conexión AMQP! Cópiala, ve a buscar nuestro archivo .env... y pégala sobre doctrine://default. También puedes poner esto en un archivo .env.local... que es lo que yo haría normalmente para evitar comprometer estas credenciales.

Tip

La URL que has copiado empezará ahora por amqps:// (¡con una "s"!). Eso es AMQP "seguro". Cámbialo a amqp:// para que las cosas funcionen. La compatibilidad con SSL se introdujo en Symfony 5.2, pero requiere una configuración adicional.

En cualquier caso, la parte amqp:// activa el transporte AMQP en Symfony... y el resto contiene un nombre de usuario, una contraseña y otros detalles de la conexión. En cuanto hagamos este cambio, nuestros dos transportes async y async_priority_high... ¡ahora utilizan RabbitMQ! ¡Ha sido fácil!

Ah, pero fíjate en que sigo utilizando doctrine para mi transporte de fallos... y voy a mantenerlo. El transporte de fallos es un tipo especial de transporte... y resulta que el tipo de transporte doctrine es el que más funciones tiene para revisar los mensajes fallidos. Puedes utilizar AMQP para esto, pero yo recomiendo Doctrine.

Antes de probar esto, quiero hacer otro cambio. Abresrc/Controller/ImagePostController.php y busca el método create(). Este es el controlador que se ejecuta cada vez que subimos una foto... y es el responsable de enviar el comando AddPonkaToImage. También añade un retraso de 500 milisegundos a través de este sello. Comenta esto por ahora... Te mostraré por qué lo hacemos un poco más tarde.

... lines 1 - 23
class ImagePostController extends AbstractController
{
... lines 26 - 40
public function create(Request $request, ValidatorInterface $validator, PhotoFileManager $photoManager, EntityManagerInterface $entityManager, MessageBusInterface $messageBus)
{
... lines 43 - 63
$envelope = new Envelope($message, [
//new DelayStamp(500)
]);
... lines 67 - 69
}
... lines 71 - 98
}

La extensión AMQP de PHP

¡Muy bien! Aparte de eliminar ese retraso, todo lo que hemos hecho es cambiar nuestra configuración de transporte de Doctrine a AMQP. Veamos... ¡si las cosas siguen funcionando! En primer lugar, asegúrate de que tu trabajador no se está ejecutando... para empezar. Luego, busca tu navegador, selecciona una foto y... ¡funciona! Bueno, espera... porque puede que te haya salido un gran error AJAX. Si es así, abre el perfilador de esa petición. Estoy bastante seguro de saber qué error verás

Se ha intentado cargar la clase "AMQPConnection" desde el espacio de nombres global. ¿Olvidaste una declaración "use"?

Pues... ¡no! Bajo el capó, el tipo de transporte AMQP de Symfony utiliza una extensión de PHP llamada... bueno... ¡amqp! Es un complemento de PHP -como xdebug o pdo_mysql- que probablemente tendrás que instalar.

Lo malo de las extensiones de PHP es que su instalación puede variar en función de tu sistema. En el caso de Ubuntu, puedes ejecutar

sudo apt-get install php-amqp

O puedes usar pecl, como hice yo con mi instalación Homebrew para Mac:

pecl install amqp

Una vez que consigas instalarla, asegúrate de reiniciar el servidor web Symfony para que vea el cambio. Si tienes problemas para configurarlo, háznoslo saber en los comentarios y haremos lo posible por ayudarte

Cuando esté todo configurado, deberías poder subir una foto sin errores. Y... como esto no tenía errores... probablemente... ¿se envió a RabbitMQ? Cuando actualizo, dice "Ponka is napping"... porque nada ha consumido nuestro mensaje todavía. Bueno, vamos a ver qué pasa. Busca tu terminal y consume los mensajes de nuestros dos transportes:

php bin/console messenger:consume -vv async_priority_high async

Y... ¡ahí está! Ha recibido el mensaje, lo ha gestionado... ¡y ya está! Cuando actualizamos la página... ¡ahí está Ponka! ¡Ha funcionado! Pasar de Doctrine a RabbitMQ fue tan sencillo como cambiar nuestra cadena de conexión.

A continuación, vamos a profundizar en lo que acaba de ocurrir entre bastidores: ¿qué significa "enviar" un mensaje a RabbitMQ o "obtener" un mensaje de él? Ah, y te van a encantar las herramientas de depuración de RabbitMQ.

Leave a comment!

21
Login or Register to join the conversation
Default user avatar

While i am creting the instance for Rabbitmq on https://api.cloudamqp.com/
It was generating the connection URL like "amqps://siyjukin:8pBsS8JxGr..." host instead of "amqp://siyjukin:8pBsS8JxGr9..."

Because of this i was geting the below error

No transport supports the given Messenger DSN "amqps://vdfhbywr:qfdJGzTGWS...".

1 Reply

Hey Balu,

Unfortunately your comment got spam filter, I just approved it. So, after you changed it from "amqps" to "amqp" - everything works fine now?

Cheers!

Reply
Stefan T. Avatar

Yep,this is solution you should note it.

Reply

Hello! How to xdebug AMQP message handler? Breakpoint not working, however if i use doctrine message handler (i previously comment in messenger.yaml line with message) xdebug work perfect P.s. I use PHPStorm with Xdebug

Reply

Hey @Mepcuk ,

I think you can use dump() debug function inside the message handle, run the Messenger worker with symfony console messenger:consume, do something to trigger that message creation and wait when the Messenger handles it, then open the console where you you're running the messenger:consume to see the dump in the console :) Thanks to the VarDump component the dump will be well-formatted in the console output :)

Another way to debug it - temporarily add MESSENGER_TRANSPORT_DSN=sync:// to your .env.local (make sure no more MESSENGER_TRANSPORT_DSN declarations there that will overwrite the var). Thanks to this message the Messenger will work sync instead of async, i.e. it will run the message handler in the same request and so you will see the dump on the Symfony's WDT. Or use dd() to stop the execution and see the dump right on the page :)

I hope this helps!

Cheers!

Reply
Tomasz-I Avatar
Tomasz-I Avatar Tomasz-I | posted hace 7 meses

Hello!
I was wondering if you have a good solution for production and development apps?
I have RabbitMQ on a server and supervisor process for handling Worker for the app from main domain (production). On the same server, on different domain a have a development version of the app. In order to user RabbitMQ there I should add another process for Supervisord with another worker so that it consumes messages from Development app. But actually these are the same messages, sent to the same RabbitMQ instance. So what should I do? Have different names for transport for both Production and Development apps? Any ideas?

Reply

Hey Tomasz,

I believe the easiest thing to do is to create another account for dev and use those credentials locally. Otherwise, you'll need to configure Messenger to send your messages to a different RabbitMQ exchange.

I hope it helps. Cheers!

Reply
Daniel W. Avatar
Daniel W. Avatar Daniel W. | posted hace 2 años

Hi, when my consumer consumes the message from rabbitMQ I get an exception:

[Symfony\Component\Messenger\Exception\MessageDecodingFailedException]
Could not decode message using PHP serialization: ��-.

Is it because the message was created and dispatched by hand in rabbitmq UI and not with symfony messenger?

Reply

Hey Daniel W.!

Woh! That's very interesting! When you dispatched the message by hand, what format did you use for the data? Was it JSON? Did you actually try to serialize a PHP object and "paste" it in?

If you used JSON, then the general answer of how to make Messenger understand custom created message bodies is a a bit later in the tutorial: https://symfonycasts.com/sc...

If you pasted a serialized object by hand, the answer is probably that you should first base64_encode the PHP class before sticking it into the body. PHP serialization creates binary characters... which don't play well in some transports. That's why we base64_encode when we encode a message - https://github.com/symfony/... - and base64_decode when we read it back in (well, technically, the decoding is optional): https://github.com/symfony/...

Let me know if that helps!

Cheers!

1 Reply
Daniel W. Avatar

Ahh I was too unpatient experimenting again. Yea I need a custom serializer for handling external messages.
I kinda expected it I just thought that if no serializer is defined the messenger would handle the content as pain text.
Thanks a lot.

Reply
Nourheine Avatar
Nourheine Avatar Nourheine | posted hace 2 años

hello , i used amqp cloud , create an instance and add its url in my messenger transport dsn, i got this error:
No transport supports the given DSN:***,
is there extra econfiguartion to add in my app ?
thanks in advance

Reply

Hey!

Are you in Symfony 5.1? The Ampq transport was move to its own library, read more info here https://symfony.com/doc/current/messenger.html#amqp-transport
if thats the case just install composer require symfony/amqp-messenger

Cheers

Reply
Default user avatar

While i am creting the instance for Rabbitmq on https://api.cloudamqp.com/
It was generating the connection URL like "amqps://siyjukin:8pBsS8JxGr..." host instead of "amqp://siyjukin:8pBsS8JxGr9..."

Because of this i was geting the below error

No transport supports the given Messenger DSN "amqps://vdfhbywr:qfdJGzTGWS...".

Reply

Hey Balu

Sorry for late reply, your comment was somehow missed in multiple comments =( So have you tried to remove this letter "s" from url? and use it?

Cheers!

Reply

My error was You cannot use the "Symfony\Component\Messenger\Transport\AmqpExt\Connection" as the "amqp" extension is not installed.

I'm using Docker with php:7.3.8-fpm-alpine3.10.

I added to my Dockerfile something equvalent to:

<br />RUN apk add rabbitmq-c rabbitmq-c-dev<br />RUN pecl install amqp<br />RUN docker-php-ext-enable amqp<br />RUN apk del --purge rabbitmq-c-dev<br />

Sould you have the same problem.

Cheers

Reply
Default user avatar
Default user avatar Henry Vallenilla | julien_bonnier | posted hace 2 años | edited

Hi julien_bonnier I am having the same issue, did you solve it? Thx

Reply

That was a while ago, but I'm pretty sure I was posting my solution and not just asking for help. Are you using Docker? If so, did you try to add the pecl package and enable the extension as mentioned in my previous post?

Reply

Hey julien_bonnier!

Hmm, I'm not sure! Here's what I would check. Open any page in your site, wait for the web debug toolbar to open, hover over the Symfony version on the bottom right, and then click View phpinfo(). This will show you the phpinfo() details. Is AMQP here? My guess is that it is not here... and so there's somethiung wrong with your Docker setup. It could be that your web browser is using a different php container (sometimes people have 2 containers for php - one for the cli and one for php-fpm) or something else.

Cheers!

Reply
Default user avatar
Default user avatar Henry Vallenilla | weaverryan | posted hace 2 años

Hi, I am having the same issue, did you solve it? Thanx

Reply

Hey Henry Vallenilla!

What operating system are you on and how do you have PHP installed? Are you using Docker? Installing PHP extensions is different for every setup, unfortunately :p.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

Este tutorial está construido con Symfony 4.3, pero funcionará bien en Symfony 4.4 o 5.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // v1.8.0
        "doctrine/doctrine-bundle": "^1.6.10", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // v2.0.0
        "doctrine/orm": "^2.5.11", // v2.6.3
        "intervention/image": "^2.4", // 2.4.2
        "league/flysystem-bundle": "^1.0", // 1.1.0
        "phpdocumentor/reflection-docblock": "^3.0|^4.0", // 4.3.1
        "sensio/framework-extra-bundle": "^5.3", // v5.3.1
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/flex": "^1.9", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/messenger": "4.3.*", // v4.3.4
        "symfony/property-access": "4.3.*", // v4.3.2
        "symfony/property-info": "4.3.*", // v4.3.2
        "symfony/serializer": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.5", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.7
        "symfony/debug-bundle": "4.3.*", // v4.3.2
        "symfony/maker-bundle": "^1.0", // v1.12.0
        "symfony/monolog-bundle": "^3.0", // v3.4.0
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/var-dumper": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2
    }
}
userVoice