Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Investigar y reintentar mensajes fallidos

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

Aparentemente, ahora que hemos configurado un failure_transport, si el tratamiento de un mensaje sigue sin funcionar después de 3 reintentos, en lugar de enviarse a /dev/null, se envían a otro transporte, en nuestro caso llamado "fallido". Ese transporte es... realmente... el mismo que cualquier otro transporte... y podríamos utilizar el comando messenger:consume para intentar procesar de nuevo esos mensajes.

Pero, hay una forma mejor. Ejecuta:

php bin/console messenger

Ver los mensajes en la cola fallida

Oye, aquí se esconden nuevos y brillantes comandos Tres en messenger:failed. Prueba el de messenger:failed:show:

php bin/console messenger:failed:show

¡Bien! Ahí están nuestros 4 mensajes fallidos... esperando a que los miremos. Imaginemos que no estamos seguros de qué ha fallado en estos mensajes y queremos comprobarlo. Empieza pasando el identificador 115:

php bin/console messenger:failed:show 115

Me encanta esto: ¡nos muestra el mensaje de error, la clase de error y un historial de las desventuras del mensaje a través de nuestro sistema! Ha fallado, ha sido redistribuido al transporte asíncrono en 05, en 06 y luego en 07, finalmente ha fallado y ha sido redistribuido al transporte failed.

Si añadimos un -vv en el comando...

php bin/console messenger:failed:show 115 -vv

Ahora podemos ver un seguimiento completo de la pila de lo que ocurrió en esa excepción.

Esta es una forma realmente poderosa de averiguar qué ha fallado y qué hacer a continuación: ¿tenemos un error en nuestra aplicación que debemos solucionar antes de volver a intentar esto? ¿O tal vez fue un fallo temporal y podemos volver a intentarlo ahora? O tal vez, por alguna razón, queramos eliminar este mensaje por completo.

Si quieres eliminarlo sin reintentarlo, ese es el comandomessenger:failed:remove.

Reintentar mensajes fallidos

Pero... ¡vamos a reintentar esto! De nuevo en el manejador, cambia esto para que falle aleatoriamente.

... lines 1 - 13
class AddPonkaToImageHandler implements MessageHandlerInterface, LoggerAwareInterface
{
... lines 16 - 30
public function __invoke(AddPonkaToImage $addPonkaToImage)
{
... lines 33 - 46
if (rand(0, 10) < 7) {
throw new \Exception('I failed randomly!!!!');
}
... lines 50 - 56
}
}

Hay dos formas de trabajar con el comando reintentar: puedes reintentar un id específico como el que ves aquí o puedes reintentar los mensajes uno a uno. Vamos a hacerlo. Ejecuta:

php bin/console messenger:failed:retry

Esto es algo parecido a cómo funciona messenger:consume, excepto que te pregunta antes de intentar cada mensaje y, en lugar de ejecutar este comando todo el tiempo en producción, lo ejecutarás manualmente cada vez que tengas algunos mensajes fallidos que necesites procesar.

¡Genial! Vemos los detalles y nos pregunta si queremos volver a intentarlo. Al igual que con show, puedes pasar -vv para ver los detalles completos del mensaje. Di "sí". Se procesa... y continúa con el siguiente. De hecho, déjame intentarlo de nuevo con -vv para que podamos ver lo que ocurre:

php bin/console messenger:failed:retry -vv

Cuando los mensajes fallidos... Vuelven a fallar

Esta vez vemos todos los detalles. Vuelve a decir "sí" y... bien: "Mensaje recibido", "Mensaje gestionado" y al siguiente mensaje. ¡Estamos en racha! Fíjate en que el identificador de este mensaje es el 117 - eso será importante en un segundo. Pulsa "Sí" para reintentar este mensaje también.

¡Vaya! ¡Esta vez ha vuelto a fallar! ¿Qué significa esto? Bueno, recuerda que el transporte de fallos es en realidad un transporte normal que utilizamos de forma especial. Y así, cuando un mensaje falla aquí, Messenger... ¡lo reintenta! Sí, ¡se envió de nuevo al transporte de fallos!

Pulsaré Control+C y volveré a ejecutar el comando show:

php bin/console messenger:failed:show

Ese id 119 no estaba allí cuando empezamos. No, cuando se procesó el mensaje 117, falló, se volvió a enviar al transporte de fallos como id 119, y luego se eliminó. Y así, a menos que cambies la configuración, los mensajes se reintentarán 3 veces en el transporte de fallos antes de ser finalmente descartados por completo.

Pero si miras el mensaje reintentado más de cerca

php bin/console messenger:failed:show 119 -vv

Hay un pequeño error: faltan el error y la clase de error. Los datos siguen en la base de datos... sólo que no se muestran correctamente aquí. Pero puedes ver el historial del mensaje: incluso que fue enviado al transporte failed y luego enviado de nuevo al transporte failed.

Por cierto, puedes pasar una opción --force al comando retry si quieres que reintente los mensajes uno a uno sin preguntarte cada vez si debe hacerlo o no. Además, no todos los tipos de transporte -como AMQP o Redis- soportan todas las características que acabamos de ver si lo usas como transporte de fallos. Eso puede cambiar en el futuro, pero en este momento -Doctrine es el transporte más robusto para usar en caso de fallos.

De todos modos, por muy chulo que sea fallar, volvamos atrás y eliminemos el código que está rompiendo nuestro manejador. Porque... es hora de dar un paso más en el funcionamiento de Messenger: es hora de hablar del middleware.

Leave a comment!

4
Login or Register to join the conversation
Default user avatar
Default user avatar Fabien Pot | posted hace 2 años

Actually, I wouldn't say "Doctrine is the most robust transport to use for failures".
1. you have one failed transport for every other transport. You can't have handle failed messages from other transports in different ways. E.g. Failed messages from transport A should be handled every 1 hour, failed messages from transport B should be handled every 5 hours.
2. you cannot move messages failed messages from transport to transport, e.g. after X retries on transport A message should be moved to less prioritized transport B not to block more prioritized transport A.
With rabbitmq you have this advantage that rabbit is acknowledged after max_retries that message was rejected and you can forward this message to the dead letter exchange (or the same exchange but different queue with another routing key) which is configured for another transport in messenger configuration.

Reply

Hey @Fabien!

That’s a very fair criticism - and your information is correct and detailed :).

What I should have said is that - in symfony - the doctrine transport as a failure transport - implements a few features that the Amqp transport does not, specifically the ability to return a list of all the items in the queue (which one of the failure commands allows you to do, if your failure transport supports listing).

Your setup / ideas with rabbitmq are indeed more robust. The failure transport in symfony is a feature you get with no effort or setup, but you’re right that it won’t do the things you’re saying :).

Cheers!

Reply
Default user avatar

Thank you Ryan. I appreciate that.

About the list. With rabbitmq, if you would use this transport for failed messages you can list failed messages via rabbitmq UI (or rabbitmqctl).
What I think the messenger component is missing is a failure transport per transport (and failure transport could also have it's failure transport and so on).

Reply

Hey Fabien!

> About the list. With rabbitmq, if you would use this transport for failed messages you can list failed messages via rabbitmq UI (or rabbitmqctl).

That’s true. Iirc it does this by reading then out and requeing them, which is ok, but we decided against doing this in messenger simply as a way to list the messages (and we still couldn’t list them all... since we wouldn’t read 10k messages and requeue then just for a list :p)

> What I think the messenger component is missing is a failure transport per transport (and failure transport could also have it's failure transport and so on).

Totally! When I implanted the failure transport in symfony 4.3, I was already a couple of days passed feature freeze and was cutting corners to get this included. The corner I cut was supporting multiple failure transport... which given enough time is not too hard to add.

There is a pull request open to add this - https://github.com/symfony/... - I need to help push it across the finish line :).

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