Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

El transporte de fallos

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

Ahora sabemos que cada mensaje se reintentará 3 veces -lo cual es configurable- y luego, si el manejo sigue fallando, será "rechazado"... lo cual es una palabra de "cola" para: se eliminará del transporte y se perderá para siempre.

Eso es... ¡un fastidio! Nuestro último reintento se produjo 14 segundos después del primero... pero si el gestor falla porque un servidor de terceros está temporalmente fuera de servicio... entonces si ese servidor está fuera de servicio aunque sólo sea durante 30 segundos... ¡el mensaje se perderá para siempre! ¡Sería mejor si pudiéramos reintentarlo una vez que el servidor volviera a funcionar!

La respuesta a esto es... ¡el transporte de fallos!

Hola Transporte de Fallas

En primer lugar, voy a descomentar un segundo transporte. En general, puedes tener tantos transportes como quieras. Éste comienza con doctrine://default. Si miras nuestro archivo .env... ¡eh! ¡Eso es exactamente lo que nuestra variable de entornoMESSENGER_TRANSPORT_DSN está configurada! Sí, tanto nuestro transporte asynccomo el nuevo failed están utilizando el transporte doctrine y la conexión de doctrina default. Pero el segundo también tiene esta pequeña opción ?queue_name=failed. Oooooooooooo.

framework:
messenger:
... lines 3 - 5
transports:
... lines 7 - 12
failed: 'doctrine://default?queue_name=failed'
... lines 14 - 20

Vuelve a lo que estés usando para inspeccionar la base de datos y comprueba la tabla de colas:

DESCRIBE messenger_messages;

Ah. Una de las columnas de esta tabla se llama queue_name. Esta columna nos permite crear varios transportes que almacenan todos los mensajes en la misma tabla. Messenger sabe qué mensajes pertenecen a cada transporte gracias a este valor. Todos los mensajes enviados al transporte failed tendrán un valor failed... que puede ser cualquier cosa - y los mensajes enviados al transporte async utilizarán el valor por defecto... que es default.

Configurar los transportes

Por cierto, cada transporte tiene una serie de opciones de conexión diferentes y hay dos formas de pasarlas: o bien como parámetros de consulta como éste, o bien mediante un formato expandido en el que pones el dsn en su propia línea y luego añades una clave options con lo que necesites debajo.

¿Qué opciones puedes poner aquí? Cada tipo de transporte -como doctrine o amqp - tiene su propio conjunto de opciones. Ahora mismo, no están bien documentadas, pero son fáciles de encontrar... si sabes dónde buscar. Por convención, cada tipo de transporte tiene una clase llamada Connection. Pulsaré Shift+Shift en PhpStorm, buscaré Connection.php... y buscaré los archivos. ¡Ahí están! Una clase Connectionpara Amqp, Doctrine y Redis.

Abre la de Doctrine. Todas estas clases tienen documentación cerca de la parte superior que describe sus opciones, en este caso: queue_name, table_name y algunas otras, como auto_setup. Antes hemos visto que Doctrine creará la tablamessenger_messages automáticamente si no existe. Si no quieres que eso ocurra, debes poner auto_setup en false.

El transporte con más opciones se puede ver en la clase Conexión Amqp. Hablaremos de Amqp más adelante en el tutorial.

El transporte_fracaso

De todos modos, ¡volvemos a ello! Ahora tenemos un nuevo transporte llamado failed... que, a pesar de su nombre, es igual que cualquier otro transporte. Si quisiéramos, podríamos encaminar allí las clases de mensajes y consumirlas, tal y como estamos haciendo con async.

Pero... el objetivo de este transporte es diferente. Cerca de la parte superior, hay otra clave llamada failure_transport. Descomenta eso y observa que apunta a nuestro nuevo transporte failed.

framework:
messenger:
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
failure_transport: failed
... lines 5 - 20

¿Qué hace? ¡Veámoslo en acción! Primero, ve a reiniciar nuestro trabajador:

php bin/console messenger:consume -vv

¡Woh! Esta vez, nos pregunta qué "receptor" -que básicamente significa qué "transporte"- queremos consumir. Un trabajador puede leer de uno o varios transportes, algo de lo que hablaremos más adelante con los transportes "priorizados". Vamos a consumir sólo el transporte async - manejaremos los mensajes del transporte failed de otra manera. Y en realidad, para facilitarnos la vida, podemos pasar async como argumento para que no nos pregunte qué transporte utilizar cada vez:

php bin/console messenger:consume -vv async

Ahora... ¡vamos a subir algunas imágenes! Entonces... por aquí... rápidamente, las 4 agotan sus reintentos y acaban siendo rechazadas por el transporte. Hasta ahora, eso significaba que habían desaparecido para siempre. Pero esta vez... eso no ocurrió. Antes de eliminar el mensaje de la cola, dice

El mensaje rechazado AddPonkaToImage se enviará al transporte de fallo "fallido"

Y luego... "Enviando mensaje". Por tanto, se ha eliminado del transporte async, pero sigue existiendo porque se ha enviado al transporte "fallido".

¿Cómo podemos ver qué mensajes han fallado y volver a intentarlo si pensamos que esos fallos eran temporales? Con un par de brillantes y nuevos comandos de consola. Hablemos de ellos a continuación.

Leave a comment!

5
Login or Register to join the conversation
Roland W. Avatar
Roland W. Avatar Roland W. | posted hace 2 años

Is there a possibility to prevent that certain messages are sent to the failure transport after being rejected?

Reply

Hey Roland W.

Yep you can do it with special exception UnrecoverableMessageHandlingException

Cheers!

Reply
Roland W. Avatar

As far as I can see the UnrecoverableMessageHandlingException ony prevents retries but not the sending to the failure transport, right?

Reply

IIRC it should discard message at all without sending it to failed, hm but why you need retries but not failure?

Reply
Roland W. Avatar
Roland W. Avatar Roland W. | sadikoff | posted hace 2 años | edited

I would have to throw it in the Message Handler, right? Unfortunatelly I guess I need something that works in an Event Subscriber to be able to call this:

if($event->willRetry() === false) { ... }

Becaus I still want the retries but not the failure transport.

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