Tâches asynchrones en Python avec Redis Queue

December 04, 2019
Rédigé par
Sam Agnew
Twilion

Tâches asynchrones en Python avec Redis Queue

RQ (Redis Queue) est une bibliothèque Python utilisant Redis pour mettre les tâches en file d'attente et les traiter en arrière-plan à l'aide de workers. Elle présente une barrière à l'entrée bien moins élevée et est beaucoup plus facile à utiliser que d'autres bibliothèques telles que Celery.

RQ, et la mise des tâches en file d'attente en général, sont un moyen idéal d'exécuter des fonctions longues ou contenant du code d'arrêt, telles que les requêtes réseau.

RQ est simple d'utilisation : il suffit de créer une file d'attente et d'y mettre la fonction désirée, accompagnée des arguments que vous voulez passer vers cette fonction, d'après le code dans leur exemple « Hello World » :

from redis import Redis
from rq import Queue

from my_module import count_words_at_url

q = Queue(connection=Redis())
result = q.enqueue(count_words_at_url, 'http://nvie.com')

Découvrons pas à pas comment utiliser RQ pour exécuter une fonction récupérant des données depuis l'API Mars Rover.

Configuration de votre environnement et installation de dépendances

Avant de continuer, vous devrez vous assurer qu'une version à jour de Python 3 et pip est installée. Assurez-vous de créer et d'activer un environnement virtuel avant d'installer toute dépendance.

Nous utiliserons la bibliothèque requests afin d'obtenir les données depuis l'API Mars Rover de la NASA, et RQ pour le traitement des tâches asynchrones. Avec votre environnement virtuel activé, exécutez la commande suivante dans votre terminal pour installer les bibliothèques Python nécessaires :

pip install rq==1.1.0 requests==2.22.0

Pour que RQ fonctionne, vous devrez installer Redis sur votre machine, ce qui peut être fait à l'aide des commandes suivantes en utilisant wget :

wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar xzf redis-5.0.5.tar.gz
cd redis-5.0.5
make

Exécutez Redis dans une fenêtre de terminal distincte sur le port par défaut avec la commande src/redis-server, depuis son répertoire d'installation.

Premiers pas avec Redis Queue

Commençons par un exemple de fonction permettant d'accéder à l'API Mars Rover et d'imprimer l'URL associée à une image prise par le rover. Le fait que cette fonction inclut de lancer une requête HTTP signifie qu'elle contient du code d'arrêt. Elle constitue dès lors un très bon exemple de l'utilité que peut avoir RQ.

Créez un fichier appelé mars.py et ajoutez-y le code suivant :

from random import choice

import requests

mars_rover_url = 'https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos'


def get_mars_photo(sol):
    params = { 'sol': sol, 'api_key': 'DEMO_KEY' }
    response = requests.get(mars_rover_url, params).json()
    photos = response['photos']

    image = choice(photos)['img_src']
    print(image)

Exécutez ce code en ouvrant un shell Python et en saisissant :

from mars import get_mars_photo
get_mars_photo(1000)

Cela imprimera une URL renvoyant à une image aléatoire prise par le Mars Rover au 1 000e jour solaire martien de son voyage. Notez qu'il faut un certain temps pour que la requête HTTP soit résolue et pour que l'URL soit imprimée. Votre code doit attendre que le serveur distant réponde à votre requête HTTP et ne peut rien faire d'autre tant que ce n'est pas le cas. Voici une photo imprimée par mon code.

Photo du rover Mars

Si vous passez cette fonction vers RQ pour qu'elle soit traitée en tant que tâche asynchrone, l'exécution du reste de votre code ne sera plus empêchée. Pour cela, rien de plus facile : il suffit d'importer RQ, de créer une file d'attente et d'y mettre la fonction. Pour ce faire, nous allons placer le code suivant dans un fichier nommé print_mars_photos.py :

from redis import Redis
from rq import Queue

from mars import get_mars_photo

q = Queue(connection=Redis())

for i in range(10):
    q.enqueue(get_mars_photo, 990 + i)

Pour que ce code fonctionne, vous devrez exécuter un worker RQ en arrière-plan dans une autre fenêtre de terminal dédiée au traitement des tâches.

Workers RQ

Un worker est un processus Python qui s'exécute généralement en arrière-plan et qui n'a d'autre utilité que d'effectuer des tâches longues ou contraignantes que vous ne souhaitez pas exécuter au sein de processus Web. RQ a recours aux workers pour effectuer les tâches qui sont ajoutées à la file d'attente.

Pour exécuter le code que nous venons d'écrire, lancez la commande rqworker dans le même répertoire que votre code dans une autre fenêtre de terminal. Ensuite, exécutez votre code avec la commande suivante :

python print_mars_photos.py

Les URL renvoyant aux photos prises sur Mars devraient être imprimées une par une, tandis que d'autres informations sur la tâche devraient s'afficher dans la fenêtre du terminal dans laquelle le worker est exécuté, et non dans celle où votre code est en cours d'exécution.

Sortie du worker RQ

Afin de vérifier que cette fonction n'entraîne pas d'arrêt, essayez d'ajouter des instructions d'impression avant et après la boucle, et d'exécuter des appels de fonction ordinaires au lieu de les mettre dans la file d'attente :

print('Before')
for i in range(10):
    get_mars_photo(990 + i)
    # q.enqueue(get_mars_photo, 990 + i)
print('After')

Chaque URL renvoyant à une photo sera alors imprimée, suivie du message « After ». Si vous revenez au code d'origine, mais que vous conservez les instructions d'impression, vous verrez les messages s'imprimer de manière presque immédiate à mesure que le code qui exécute les requêtes HTTP est traité dans votre autre fenêtre de terminal.

Les workers RQ permettent de réaliser de nombreuses opérations, cela vaut la peine de lire la documentation pour en savoir plus. RQ s'accompagne également d'une multitude d'utilitaires permettant de mieux comprendre les tâches, notamment une interface de ligne de commande qui facilite la remise en file d'attente des tâches qui ont échoué. En voici un exemple :

# This command will requeue all jobs in myqueue's failed job registry
rq requeue --queue myqueue -u redis://localhost:6379 --all

Vers l'infini et au-delà

Les fragments de code que nous avons utilisés ici ne sont que de courts exemples de ce que vous pouvez faire avec RQ, mais j'espère qu'en lisant ce post et la documentation de RQ, vous verrez à quel point il est facile d'ajouter RQ à vos autres projets. Maintenant que vous savez exécuter des fonctions comme nous l'avons fait ci-dessus, ajouter RQ à votre app Flask ou Django ne vous demandera pas plus que d'écrire quelques lignes de code là où ce sera nécessaire !

Si vous souhaitez pouvoir planifier des tâches, vous pouvez également utiliser RQ Scheduler. Facile à ajouter aux projets utilisant déjà RQ, il présente une API simple qui vous permet d'exécuter des fonctions Python à une date/heure données.

Pour plus d'exemples de projets qui utilisent RQ, consultez ce post sur la manière de recevoir des messages texte lorsque la Station spatiale internationale passe au-dessus de votre emplacement géographique, ou celui-ci sur la manière de créer un numéro de téléphone jouant de la musique générée par ordinateur à la manière des bandes originales d'anciens jeux Nintendo.