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 SubscribeEntonces... ¿cómo funciona todo esto en producción? En realidad es un problema sencillo: en producción, tenemos que asegurarnos de alguna manera de que este comando - messenger:consume
- se ejecute siempre. Como, siempre.
Algunas plataformas de alojamiento -como SymfonyCloud- te permiten hacerlo con una sencilla configuración. Básicamente dices:
¡Oye, proveedor de la nube! Por favor, asegúrate de que
bin/console messenger:consume
esté siempre en marcha. Si se cierra por alguna razón, inicia uno nuevo.
Si no utilizas una plataforma de alojamiento así, no pasa nada, pero tendrás que hacer un poco de trabajo para obtener el mismo resultado. Y en realidad, no es sólo que necesitemos una forma de asegurarnos de que alguien inicie este comando y luego se ejecute para siempre. En realidad no queremos que el comando se ejecute para siempre. Por muy bien que escribas tu código PHP, éste no está hecho para ejecutarse eternamente: con el tiempo, su huella de memoria aumentará demasiado y el proceso morirá. Y... ¡eso es perfecto! No queremos que nuestro proceso se ejecute para siempre. No: lo que realmente queremos es que messenger:consume
se ejecute, maneje... unos cuantos mensajes... y luego se cierre. Luego, utilizaremos una herramienta diferente para asegurarnos de que cada vez que el proceso desaparezca, se reinicie.
La herramienta que hace eso se llama supervisor. Después de instalarla, le das un comando que quieres que se ejecute siempre y se queda despierto toda la noche comiendo pizza constantemente y vigilando que ese comando se ejecute. En el momento en que deja de ejecutarse, por cualquier motivo, deja la pizza y reinicia el comando.
Así que vamos a ver cómo funciona el Supervisor y cómo podemos utilizarlo para asegurarnos de que nuestro trabajador está siempre en marcha. Como estoy utilizando un Mac, ya he instalado el Supervisor a través de Brew. Si usas Ubuntu, puedes instalarlo mediante apt. Por cierto, en realidad no necesitas instalar y configurar Supervisor en tu máquina local: sólo lo necesitas en producción. Lo instalamos para poder probar y asegurarnos de que todo funciona.
Para ponerlo en marcha, necesitamos un archivo de configuración del supervisor. Busca en Google "Messenger Symfony" y abre la documentación principal. En el medio... hay un punto que habla del supervisor. Copia el archivo de configuración. Podemos ponerlo en cualquier sitio: no es necesario que viva en nuestro proyecto. Pero, a mí me gusta tenerlo en mi repo para poder guardarlo en git. En... qué tal config/
, crea un nuevo archivo llamado messenger-worker.ini
y pega el código dentro.
[program:messenger-consume] | |
command=php /path/to/your/app/bin/console messenger:consume async --time-limit=3600 | |
user=ubuntu | |
numprocs=2 | |
autostart=true | |
autorestart=true | |
process_name=%(program_name)s_%(process_num)02d |
El archivo le dice al Supervisor qué comando debe ejecutar y otra información importante como el usuario con el que debe ejecutar el proceso y el número de procesos a ejecutar. Esto creará dos procesos trabajadores. Cuantos más trabajadores se ejecuten, más mensajes se podrán gestionar a la vez. Pero también, más memoria y CPU necesitarás.
Ahora, localmente, no necesito ejecutar el supervisor... porque podemos ejecutar manualmentemessenger:consume
. Pero para asegurarnos de que todo esto funciona, vamos a fingir que mi ordenador es de producción y cambiar la ruta para que apunte a usar mi ruta local:/Users/weaverryan/messenger
... que si vuelvo a comprobar en mi terminal... oop - me olvidé de la parte Sites/
. Luego, aquí abajo, cambiaré el usuario para que sea weaverryan
. De nuevo, normalmente lo establecerás con tus valores de producción.
Ah, y si te fijas bien en el comando, se está ejecutandomessenger:consume async
. Asegúrate de consumir también async_priority_high
. El comando también tiene una opción --time-limit=3600
. Hablaremos más de esto y de otras opciones dentro de un rato, pero esto es genial: le dice al trabajador que se ejecute durante 60 minutos y luego salga, para asegurarse de que no envejece demasiado y ocupa demasiada memoria. En cuanto salga, el Supervisor lo reiniciará.
Ahora que tenemos nuestro archivo de configuración, tenemos que asegurarnos de que el Supervisor puede verlo. Cada instalación del Supervisor tiene un archivo de configuración principal. En un Mac donde se instala a través de Brew, ese archivo se encuentra en /usr/local/etc/supervisord.ini
. En Ubuntu, debería estar en /etc/supervisor/supervisord.conf
.
Entonces, en algún lugar de tu archivo de configuración, encontrarás una sección include
con una líneafiles
. Esto significa que el Supervisor está buscando en este directorio archivos de configuración -como el nuestro- que le dirán qué hacer.
Para meter nuestro archivo de configuración en ese directorio, podemos crear un enlace simbólico:ln -s ~/Sites/messenger/config/messenger-worker.ini
y luego pegar el directorio.
ln -s ~/Sites/messenger/config/messenger-worker.ini /usr/local/etc/supervisor.d/
Ya está Ahora el supervisor debería poder ver nuestro archivo de configuración. Para ejecutar el supervisor, utilizaremos algo llamado supervisorctl
. Como estoy en un Mac, también necesito pasar una opción -c
y apuntar al archivo de configuración que acabamos de ver. Si estás en Ubuntu, no deberías necesitar hacer esto: ya sabrá dónde buscar. Luego di reread
: eso le dice al Supervisor que vuelva a leer los archivos de configuración:
supervisorctl -c /usr/local/etc/supervisord.ini reread
Por cierto, es posible que tengas que ejecutar este comando con sudo
. Si lo haces, no pasa nada: ejecutará los procesos propiamente dichos como el usuario de tu archivo de configuración.
¡Genial! Ve el nuevo grupo messager-consume
. Ese nombre procede de la clave que hay en la parte superior de nuestro archivo. A continuación, ejecuta el comando update
... que reiniciará los procesos con la nueva configuración... si ya estuvieran en marcha... pero los nuestros aún no lo están:
supervisorctl -c /usr/local/etc/supervisord.ini update
Para iniciarlos, ejecuta start messenger-consume:*
:
supervisorctl -c /usr/local/etc/supervisord.ini start messenger-consume:*
El último argumento - messenger-consume:*
no es muy obvio. Cuando creas un "programa" llamado messenger-consume
, se crea lo que se llama un "grupo de procesos homogéneos". Como tenemos processes=2
, este grupo ejecutará dos procesos. Al decir messenger-consume:*
le dice al Supervisor que inicie todos los procesos dentro de ese grupo.
Cuando lo ejecutamos... no dice nada... pero... ¡nuestros comandos de trabajador deberían estar ejecutándose ahora! Vamos a detener nuestro trabajador manual para que sólo se ejecuten los del Supervisor. Ahora,
tail -f var/log/messenger.log
Esto hará que sea realmente obvio si nuestros mensajes están siendo gestionados por esos trabajadores. Ahora, sube unas cuantas fotos, borra un par de elementos, muévete y... ¡sí! ¡Está funcionando! En realidad está funcionando casi el doble de rápido de lo normal porque tenemos el doble de trabajadores.
Y ahora podemos divertirnos un poco. En primer lugar, podemos ver los identificadores de proceso creados por el Supervisor ejecutando:
ps -A | grep messenger:consume
Tip
También puedes utilizar ps aux
, que funcionará en más sistemas operativos.
Ahí están: 19915 y 19916. Matemos uno de ellos:
kill 19915
Y ejecutémoslo de nuevo:
ps -A | grep messenger:consume
¡Sí! 19916 sigue ahí, pero como hemos matado al otro, el supervisor ha iniciado un nuevo proceso para él: 19995. El supervisor es genial.
A continuación, vamos a hablar más sobre las opciones que podemos utilizar para hacer salir a los trabajadores a propósito antes de que ocupen demasiada memoria. También hablaremos de cómo reiniciar los trabajadores al desplegarlos para que vean el nuevo código y un pequeño detalle sobre cómo pueden romperse las cosas si actualizas tu clase de mensajes.
Hey Tim,
Thank you for sharing your workaround with others! Good idea to start the command via CRON ;)
Cheers!
Hello again !
A tiny precision, wich I faced : on ubuntu server I had to precise the directory where the supervisor should find the app so my worker looks like :
[program:messenger-consume]
command=php bin/console messenger:consume async --time-limit=3600
directory=/var/www/html/my-app
One other thing about supervisor, it seems he prefer now working with .conf files and not .ini (easy to fix, but you have to think about this --> http://supervisord.org/runn...
Are aware of a trouble for the FlySystem to delete files in a handler? I can't do it on production while I'm ok on local dev (still searching btw)
Hey Remi,
Yeah, it makes sense. In our example we use the absolute path so it works. If you use a relative path like only "bin/console" - you have to specify the directory of your project. Thank you for some tips! Might be useful for other users.
About problem with deleting files in a handler. Hm, most probably you have different permissions, so it sounds like permission problem. Is it for all the folders? Or some of them are removed successfully but some are not?
Cheers!
Hm, most probably you have different permissions, so it sounds like permission problem. Is it for all the folders? Or some of them are removed successfully but some are not?
Mmm... Not sure because my filesystem works perfectly in the controller in production... I'll explore further and keep you update (i didn't try it syncronously yet to make a differential diagnosis)
Hey @Rémi Dck
I also believe that the file deletion problem is due to permissions. I think you just have to tell supervisor to run your worker as your web user (usually named as "www"). Here is an example of how to do it but I didn't give it a try: https://stackoverflow.com/a...
Cheers!
OMG you're the best Diego. Thanks a lot !!!
I dug in and the tail is a little bit tricky (to me, i'm not a unix user...)
The first step is finding the web user, like you said and the best command for this is less /etc/passwd
. For Ubuntu 18.04 seems to be www-data
Next in messenger-worker.conf
change user name and add environnement targeting the home directory of www-data
Aaaand done ! Love it.
Awesome! Excellent job man because I wrote the name wrong, it usually is www-data
not just www :p
Hello guys,
I'm using Docker for deployment and my image have supervisor installed within. Now in order to execute these three command :
supervisorctl reread
supervisorctl update
supervisorctl start messenger-consume:*
Where should I put them? In Dockerfile or a entrypoint command? They should be run after the container is up if I'm not wrong ?
Yo @Dang!
Hmm. I am FAR from an expert on Docker, especially in a production environment. However, I believe that you would have a specific (PHP) container set up for running supervisor and that supervisor would be your main CMD
on that container (and you'd run supervisor with nodaemon=true
). Again, this is NOT my area of expertise, so I could be misguided, but that's my impression :).
Cheers!
Hi. What is/are the possible strategy/strategies to use if the Consumer throws an exception for any reason ? Assuming that the environment is Production. Thanks.
Hey Marko,
The best strategy is to do a few more retries after some time, and if still no success - move that message into a failed messages queue where you can retry the message manually when the problem will be fixed. That's the strategy we use on SymfonyCasts, and IIRC we covered it in this course :)
Cheers!
Hello Victor! Thanks a lot for your quick answer. Does Supervisor have any role in this issue (in production) ?
Hey Marko,
Supervisor is about a different problem, it helps to restart the worker on the server, so it's a bit different. If for some reasons you mean that your worker crashes, i.e. stops on the production that you have to restart it manually again - then yes, you need that supervisor that will restart the worker for you. But the problems you described relates to different things if I understand you correctly.
Cheers!
Rainer S.
Yeah that's a good question, I'm hot sure but probably this link https://github.com/alexsilv... will help you, but I think you already saw this link. Unfortunately I can't advise you some real production example for windows, also there is not so many windows servers, that's why there is not so many solutions for it!
Cheers! Hope it will help!
Has anyone by any chance run supervisor on an AWS EB setup? (Elastic Beanstalk, using .ebextension files)
Hey Chad,
Unfortunately, I have not run it personally. Though it looks like there's some solutions around the internet, e.g.: https://gist.github.com/vra...
I hope this helps!
Cheers!
Hey Chad,
Ah, great! Sorry, can't say something more about it, have never done it before. Probably someone who use it could suggest you a good tip about it.
Cheers!
the following .ebextension
config file is working to install supervisor and start messenger:consume, but the .env
file is required to be populated with all the database credentials and such
files:
"/tmp/messenger-consume.conf":
mode: "000644"
owner: root
group: root
content: |
[program:messenger-consume]
command=php /var/www/html/bin/console messenger:consume async --time-limit=1800 --memory-limit=340M --sleep=30
user=webapp
numprocs=1
autostart=true
autorestart=true
process_name=%(program_name)s_%(process_num)02d
"/tmp/supervisord.conf":
mode: "000644"
owner: root
group: root
content: |
; supervisor config file
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)
[supervisord]
user=root
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[include]
files = /etc/supervisor/conf.d/*.conf
; Change according to your configurations
commands:
01_01_install_supervisord:
command: |
sudo easy_install supervisor
sudo mkdir -p /etc/supervisor/conf.d/
sudo mkdir -p /var/log/supervisor
sudo touch /var/log/supervisor/supervisord.log
ignoreErrors: true
container_commands:
01_01_copy_supervisor_configuration_files:
command: |
sudo mv /tmp/supervisord.conf /etc/supervisord.conf
sudo mv /tmp/messenger-consume.conf /etc/supervisor/conf.d/messenger-consume.conf
ignoreErrors: true
02_01_start_supervisord:
command: |
sudo /usr/local/bin/supervisord -c /etc/supervisord.conf
sudo /usr/local/bin/supervisorctl reread
sudo /usr/local/bin/supervisorctl update
sudo /usr/local/bin/supervisorctl restart all
sudo /usr/local/bin/supervisorctl start messenger-consume:*
ignoreErrors: true```
Hey Chad,
Thank you for sharing this solution with others! I'm glad you were able to get it working.
Cheers!
Hello,
When running supervisorctl -c /usr/local/etc/supervisord.ini start messenger-consume:*
, I get zsh: no matches found: messenger-consume:*
. But it seems the workers are actually working, because I tried to upload an image and it worked. In the video, there's no message after you start the supervisor with messenger-consume:*. So I'm just wondering if I'm doing something wrong...
Thanks!
Hey Ajie62!
Hmm. Try this command to see what's going on:
supervisorctl -c /usr/local/etc/supervisord.ini start
I definitely don't like that "no matches found". Also try using the stop
command to see if it stops anything. Here's a screenshot of what starting and stopping looks on my machine - you can see how "start" sometimes doesn't have any output (if it's already started) but sometimes does.
Also, the zsh: no matches found: messenger-consume:*
is odd... the error should come from supervisorctl... not from "zsh". Try surrounding messenger-consume:*
with quotes. I don't know zsh well... but I'm wondering if it's somehow interpreting something differently...
Cheers!
Hey, thanks for you answer! The problem was oh-my-zsh. I removed it an now everything works fine. I assume I could have solved the problem and keep oh-my-zsh, but I wanted to save time and keep learning :) Thank you again, weaverryan
// 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
}
}
In case anybody has the same problem, that the installation of SUPERVISOR is not allowed on production-server, I solved this with a workaround by using a cronjob (starting every 5 minutes) with this bash-script:
Happy coding!