If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeCuando empiezas a manejar cosas de forma asíncrona, pensar en lo que ocurre cuando el código falla es aún más importante ¿Por qué? Bueno, cuando manejas las cosas de forma sincrónica, si algo falla, normalmente falla todo el proceso, no sólo la mitad. O, al menos, puedes hacer que todo el proceso falle si lo necesitas.
Por ejemplo: imagina que todo nuestro código sigue siendo síncrono: guardamos el ImagePost
en la base de datos, pero luego, aquí abajo, falla la adición de Ponka a la imagen... porque está durmiendo la siesta. En este momento, eso supondría la mitad del trabajo realizado... lo que, dependiendo de lo sensible que sea tu aplicación, puede o no ser un gran problema. Si lo es, puedes resolverlo envolviendo todo esto en una transacción de base de datos.
Pensar en cómo van a fallar las cosas -y codificar a la defensiva cuando lo necesites- es simplemente una práctica de programación saludable.
Pero todo esto cambia cuando el código es asíncrono Piénsalo: guardamos el ImagePost
en la base de datos, se envía AddPonkaToImage
al transporte y se devuelve la respuesta con éxito. Luego, unos segundos después, nuestro trabajador procesa ese mensaje y, debido a un problema temporal de la red, ¡el manejador lanza una excepción!
Esto no es una buena situación. El usuario piensa que todo ha ido bien porque no ha visto un error. Y ahora tenemos un ImagePost
en la base de datos... pero Ponka nunca se añadirá a él. Ponka está furioso.
La cuestión es: cuando se envía un mensaje a un transporte, tenemos que asegurarnos de que el mensaje se procesa finalmente. Si no lo es, podría dar lugar a algunas condiciones extrañas en nuestro sistema.
Así que empecemos a hacer que nuestro código falle para ver qué ocurre Dentro deAddPonkaToImageHandler
, justo antes de que se ejecute el código real, digamos que si rand(0, 10) < 7
, entonces lanza un new \Exception()
con:
¡¡¡¡> He fallado 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 | |
} | |
} |
¡Veamos qué ocurre! Primero, ve a reiniciar el trabajador:
php bin/console messenger:consume -vv
Luego despejaré la pantalla y... ¡a cargar! ¿Qué tal cinco fotos? ¡Vuelve a ver lo que pasa! ¡Whoa! Están pasando muchas cosas. Vamos a desmontar esto.
El primer mensaje se recibió y se gestionó. El segundo mensaje se recibió y también se gestionó con éxito. El tercer mensaje se recibió, pero se produjo una excepción al manejarlo: "¡Fallo aleatorio!". Luego dice: "Reintento - reintento nº 1" seguido de "Enviando mensaje". Sí, como ha fallado, Messenger lo "reintenta" automáticamente... ¡lo que significa literalmente que devuelve ese mensaje a la cola para que se procese más tarde! Uno de estos registros de "Mensaje recibido" de aquí abajo es en realidad ese mensaje que se recibe por segunda vez, gracias al reintento. Lo bueno es que... ¡al final todos los mensajes se gestionaron con éxito! Por eso los reintentos molan. Podemos ver esto cuando refrescamos: todos tienen una foto de Ponka... aunque algunos hayan fallado al principio.
Pero... vamos a intentarlo de nuevo... porque ese ejemplo no mostraba el caso más interesante. Esta vez voy a seleccionar todas las fotos... oh, pero antes, vamos a limpiar la pantalla de nuestro terminal de trabajador. Vale, sube, entonces... muévete.
Allá vamos: esta vez... gracias a la aleatoriedad, vemos muchos más fallos. Vemos que un par de mensajes fallaron y se enviaron para el reintento nº 1. Luego, algunos de esos mensajes volvieron a fallar y se enviaron para el reintento nº 2. Y... ¡sí! Volvieron a fallar y se enviaron para el reintento nº 3. Finalmente... oh sí, perfecto: después de intentarlo una vez y reintentarlo 3 veces más, uno de los mensajes siguió fallando. Esta vez, en lugar de enviarse para el reintento nº 4, dice
Rechazando AddPonkaToImage (eliminando del transporte)
Esto es lo que ocurre: por defecto, Messenger reintenta un mensaje tres veces, y si sigue fallando, finalmente lo elimina del transporte y el mensaje se pierde definitivamente. Bueno... eso no es del todo cierto... y aquí pasa algo más de lo que parece a primera vista.
Además, si te fijas bien... estos reintentos se retrasan cada vez más. Vamos a aprender por qué y cómo tomar el control total sobre cómo se reintentan tus mensajes.
Hey Fernando A.!
Yes, in Symfony 4.4.12 and higher, only the most recent RedeliveryStamp is kept precisely due to this problem - https://github.com/symfony/...
However, in 5.2.0, there is a new ErrorDetailsStamp which tries to keep a history of past failures with this message: https://github.com/symfony/...
I don't know if you had a question... and if you did... if I answered it - so let me know :).
Cheers!
Hi, sorry for not specifying.
I was having an error on rabbitMQ when I was triying to send a message on a third retry, the error was "Table to large" and I concluded that the reason was because the stamp array was really big.
Im using my custom serializer, so the method to include ALL the stamps was causing this problema.
So, to solve this I just included on the custom serializer the LAST RedeliveryStamp, and the problem was solved, no more "Table to large" errors.
There is another problem when you make a query to database and it fails, you will be getting "Entity manager closed" error. I discovered that adding "--failure-limit=1" to worker command was the "official solution" for the problem, so every time that a failure occurs, the message is send to for retrying and worker is closed, and if you have supervisor, a new worker will be created, with a new db connection.
Thank you for your videos, I learn how to use messenger with your tutorials, the best $12 ever spent.
Thanks for your kind words Fernando! I'm glad to hear that you could fix your problem. Cheers!
// 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
}
}
Hi, you can only get the las redelivery stamp using
$allStamps = [$envelope->last(RedeliveryStamp::class)];
So, with this method you will not getting "Table to large" on rabbitmq.