[PYTHON] Implémentation du traitement asynchrone dans Django (Celery, Redis)

À propos de cet article

Lors de la création d'une application web avec Django, je pense que le traitement asynchrone est essentiel, et je voudrais présenter son introduction avec des exemples. Le logiciel utilisé est Celery (céleri),

environnement

・ Python 3.6.8 ・ Django 2.2.7 ・ CentOS7

Qu'est-ce que le traitement asynchrone?

En fait, je ne connaissais pas le mot asynchrone avant de faire face au problème. Ou plutôt, j'ai l'impression que je n'ai même pas remarqué que ce type de traitement en lui-même n'est plus la valeur par défaut. Par exemple, disons que vous avez une application qui met une valeur dans un formulaire et renvoie la valeur via une API ou quelque chose. kizi1.png Il n'y a pas lieu de s'inquiéter car il y a une réponse immédiate s'il n'y a qu'un seul processus. Mais que se passe-t-il si vous exécutez beaucoup de traitements à la fois et qu'il faut 30 minutes pour répondre? kizi2.png L'utilisateur doit garder le navigateur ouvert jusqu'à ce que les résultats soient renvoyés. Si vous passez à une autre page ou fermez le navigateur, le processus s'arrêtera. Voici le ** traitement asynchrone **. En lançant une tâche qui prend beaucoup de temps pour répondre au traitement asynchrone, il continuera le traitement en arrière-plan et retournera le résultat, que le navigateur soit fermé ou non.

Introduction du céleri / Redis

Des explications plus détaillées sont soigneusement écrites sur le site présenté ci-dessus, donc ici. Maintenant, j'aimerais réellement installer Celery sur le serveur et travailler avec Django. Premiers éléments nécessaires L'environnement d'installation et d'exécution est Centos7

CentOS


pip3 install celery
pip3 install django-celery-results
yum -y install epel-release
yum search redis
yum install -y redis

Le dernier que j'ai mis en place s'appelle Redis (serveur de dictionnaire distant). Il semble qu'il puisse être utilisé à diverses fins lorsque j'enquête, mais cette fois je lui demanderai de travailler comme courtier. Le flux est qu'une tâche est générée à partir de l'avant ⇒ Redis passe la tâche à Celery ⇒ Celery exécute la tâche. À propos, il semble que RabbitMQ puisse également être utilisé comme courtier à l'exception de Redis. Vous n'avez pas besoin de jouer avec le fichier de configuration pour utiliser Celery et Redis. Redis

CentOS


redis-server

Si vous tapez, il démarrera. Je pense que c'est correct de jouer avec cela en fonction de l'application, mais je n'ai pas besoin de configurer quoi que ce soit pour mon propre usage.

Coopération entre Django et Celery

Le fichier de projet Django créé pour les tests est le suivant

Django


Project
├── manage.py
├── db.sqlite3
├── Tool
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── models.py
│   ├── templates
│   │   └── main
│   │       └── celery.html
│   ├── urls.py
│   └── views.py
└── Site
    ├── __init__.py
    ├── celery.py
    ├── settings.py
    ├── tasks.py
    ├── urls.py
    └── wsgi.py

Tout d'abord, décrivez les paramètres du céleri dans settings.py.

Project/Site/settings.py


INSTALLED_APPS = [
・ ・ ・
・ ・ ・
    'django_celery_results',
]
・ ・ ・
#Paramètres de céleri
CELERY_BROKER_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/1')
CELERY_RESULT_BACKEND = "django-db"

J'ai rendu Redis disponible en tant que courtier et l'ai configuré pour enregistrer le résultat de l'exécution du travail dans la base de données. Ensuite, créez celery.py dans la même hiérarchie que settings.py et écrivez ce qui suit.

Project/Site/celery.py



import os
from celery import Celery

#Fichier de configuration Django utilisé par céleri(settings.py)Spécifier
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Site.settings')★

app = Celery('Site')★

#Déclaration d'utiliser le fichier de configuration de Django comme configuration de céleri, vous pouvez également créer un fichier de configuration pour le céleri.
app.config_from_object('django.conf:settings', namespace='CELERY')

# Load task modules from all registered Django app configs.
app.autodiscover_tasks()

★ L'endroit que j'ai ajouté est celui que j'ai édité pour cette fois. D'autres sont tirés de Officiel De plus, ajoutez ce qui suit à «__init __. Py» dans la même hiérarchie.

Project/Site/__init__.py



from __future__ import absolute_import, unicode_literals

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

__all__ = ('celery_app',)

Vous êtes maintenant prêt à partir. J'utilise le battement de céleri dans un projet réel, mais les paramètres sont les mêmes que ci-dessus. Je suis très reconnaissant de pouvoir utiliser la version officielle telle quelle.

création de tâche / écran

Ensuite, créez la tâche que vous souhaitez exécuter dans le céleri. Créez tasks.py dans la même hiérarchie que setting.py et écrivez le traitement que vous souhaitez exécuter de manière asynchrone. Je ne peux pas penser à un bon exemple cette fois, alors je vais écrire la tâche selon la formule.

Project/Site/tasks.py



from __future__ import absolute_import, unicode_literals
from celery import shared_task
import time

@shared_task
def add(x, y):
    print("En traitement")
    z = x + y
    time.sleep(10)
    print("Traitement terminé")
    return z

L'important est le décorateur @ shared_task, qui est reconnu comme une tâche de céleri en l'ajoutant à la fonction. Après cela, modifiez l'écran pour exécuter ceci et la connexion (urls.py) pour configurer l'environnement de test.

Tout d'abord, créez un écran.

Project/Tool/templates/main/celery.html


<html lang="ja">
    <head>
	  <title>{% block title %}test de céleri{% endblock %}</title>
	  <meta http-equiv="refresh" content="10; URL="> 
	</head>        
	<body>           
	  <h1>Même les débutants veulent faire un traitement asynchrone</h1>                
			<p>Entrez une valeur</p>
			<form method="post" action="celery">
				{% csrf_token %}
			  <tbody>
				<td><input type="text" name="input_a"></td>
				<td>+</td>
				<td><input type="text" name="input_b"></td>
			  </tbody>
			  <input type="submit" name="add_button" value="Calcul!">
			</form>    
	  <h1>résultat!</h1>
	        <tbody>        
				<td>Valeur de sortie:</td>
				<td>{{ result }}</td>
			</tbody>
	</body>
</html>

kizi3.png C'est comme ça. Vous pouvez le concevoir avec bootstrap, mais je me fiche de la conception cette fois-ci pour que vous puissiez l'utiliser tel quel même si vous le copiez.

Puis mettez à jour views.py.

Project/Tool/views.py


from django.shortcuts import render
from django.http import HttpResponse
from django_celery_results.models import TaskResult
from YMD_Site.tasks import add

def celery(requests):
    template = loader.get_template('main/celery.html')
    if 'add_button' in requests.POST:
        x = int(requests.POST['input_a'])
        y = int(requests.POST["input_b"])
        task_id = add.delay(x,y)★
    
    result = list(TaskResult.objects.all().values_list("result",flat=True))
    if len(result) == 0:
        resilt[0] = 0
    context = {'result': result[0]}
    return HttpResponse(template.render(context, requests))

Je vais vous expliquer un peu. Bien qu'il soit marqué d'une étoile, ajoutez .delay lors de l'exécution d'une tâche (fonction) de traitement asynchrone.

Project/Tool/views.py


    result = list(TaskResult.objects.all().values_list("result",flat=True))
    if len(result) == 0:
        resilt[0] = 0
    context = {'result': result[0]}

Concernant cette partie, le résultat traité par Céleri est sauvegardé dans une table dédiée (django_celery_results_taskresult). Cette fois, j'essaye d'obtenir le résultat à partir de là.

Enfin éditez la partie connexion,

Project/Tool/urls.py


urlpatterns = [
    path('celery', views.celery),
]

Ceci termine l'édition du code. Après cela, diverses exécutions sont effectuées sur le serveur!

Courir

Lors de l'exécution, migrez la base de données. La table de céleri sera créée automatiquement par migration.

CentOS


[root@test1 Project]Python3 manage.py makemigrations
[root@test1 Project]Python3 manage.py migarate

OK si vous avez une table de céleri

Ensuite, démarrez Redis,

CentOS


[root@test1 Project]# redis-server
8066:C 02 Dec 15:17:51.448 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
8066:M 02 Dec 15:17:51.449 * Increased maximum number of open files to 10032 (it was originally set to 4096).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 3.2.12 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 8066
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

8066:M 02 Dec 15:17:51.451 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
8066:M 02 Dec 15:17:51.451 # Server started, Redis version 3.2.12

OK si quelque chose comme ça sort

Puis commencez le céleri. Exécutez la commande Celery dans le dossier où se trouve manage.py.

CentOS


[root@test1 Project]# celery -A Site worker -l info
=====FINISH=====
/usr/local/lib/python3.6/site-packages/celery/platforms.py:801: RuntimeWarning: You're running the worker with superuser privileges: this is
absolutely not recommended!

Please specify a different user using the --uid option.

User information: uid=0 euid=0 gid=0 egid=0

  uid=uid, euid=euid, gid=gid, egid=egid,
 
 -------------- celery@test1 v4.3.0 (rhubarb)
---- **** ----- 
--- * ***  * -- Linux-3.10.0-957.el7.x86_64-x86_64-with-centos-7.6.1810-Core 2019-12-03 07:07:54
-- * - **** --- 
- ** ---------- [config]
- ** ---------- .> app:         Site:0x7f7a57d825f8
- ** ---------- .> transport:   redis://localhost:6379/1
- ** ---------- .> results:     
- *** --- * --- .> concurrency: 1 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery
                

[tasks]
  . YMD_Site.tasks.add

[2019-12-03 07:07:54,586: INFO/MainProcess] Connected to redis://localhost:6379/1
[2019-12-03 07:07:54,597: INFO/MainProcess] mingle: searching for neighbors
[2019-12-03 07:07:55,625: INFO/MainProcess] mingle: all alone
[2019-12-03 07:07:55,644: WARNING/MainProcess] /usr/local/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
  warnings.warn('Using settings.DEBUG leads to a memory leak, never '
[2019-12-03 07:07:55,645: INFO/MainProcess] celery@test1 ready.

La commande celery -A Site worker -l info, mais la dernière -l info vous permet de spécifier le niveau de journalisation à afficher. (ERREUR etc.) Remplacez la partie Site de la commande par le nom de votre propre projet, le cas échéant.

Enfin (beaucoup d'entre eux démarrent ...) exécutez le serveur et essayez d'entrer dans la page.

CentOS


[root@test1 Project]# python3 manage.py runserver 0.0.0.0:8000

Après avoir confirmé qu'il a démarré avec succès, accédez à http: // adresse IP du serveur: 8000 / céleri!

kizi3.png Il sera affiché comme ceci.

Entrez une valeur et appuyez sur Calculer. kizi4.png

C'est le point, mais lorsque vous appuyez sur le bouton, la page se recharge. Ce processus se met en veille pendant 10 secondes après le calcul, le résultat n'est donc pas renvoyé immédiatement, mais la page ne passe pas en état de veille et il n'y a pas de problème même si vous fermez le navigateur. Après avoir appuyé sur le bouton, vous pouvez voir que le serveur continue le traitement en coulisses. Si vous attendez un moment kizi5.png Le résultat est de retour!

Au fait, sur l'écran Céleri côté serveur

CentOS


[2019-12-03 07:24:02,046: INFO/MainProcess] Received task: Site.tasks.add[52948e50-4803-4c11-87ab-8eab088e1d7d]  
[2019-12-03 07:24:02,053: WARNING/ForkPoolWorker-1]En traitement
[2019-12-03 07:24:12,064: WARNING/ForkPoolWorker-1]Traitement terminé
[2019-12-03 07:24:12,135: INFO/ForkPoolWorker-1] Task Site.tasks.add[52948e50-4803-4c11-87ab-8eab088e1d7d] succeeded in 10.082466656996985s: 2

Vous pouvez voir que le processus fonctionne correctement. Aussi, lorsque vous entrez dans l'écran de gestion de Django, une page est automatiquement créée et vous pouvez vérifier le contenu du traitement. kizi6.png

Résumé

Cette fois, le but était d'essayer le traitement asynchrone, j'ai donc créé l'écran, etc. de manière relativement appropriée. Le code HTML est traité pour s'actualiser automatiquement toutes les 10 secondes. Car si vous le mettez à jour manuellement pour refléter le résultat à l'écran, le formulaire sera resoumis ... Il semble que vous puissiez prendre des mesures autour de cela, mais cette fois ce n'est pas pertinent, donc je l'ai omis.

En fait, je voulais inclure le traitement en série, le traitement parallèle et le multi-démarrage des workers, mais c'est déjà devenu assez long, donc je l'écrirai dans un autre article. De plus, il y a de nombreux problèmes à résoudre lors de son utilisation, alors j'espère pouvoir également écrire à ce sujet. Merci à tous ceux qui ont lu jusqu'ici.

Recommended Posts

Implémentation du traitement asynchrone dans Django (Celery, Redis)
Traitement asynchrone du céleri dans Flask
Traitement asynchrone (threading) en python
Traitement asynchrone avec LINE BOT: RQ (Redis Queue) en Python
Implémentation de la fonction de connexion dans Django
Implémentation du bouton like dans Django + Ajax
Flux d'obtention du résultat du traitement asynchrone à l'aide de Django et Celery
Traitement asynchrone à l'aide de Linebot dans la file d'attente des travaux
Traitement asynchrone en Python: référence inverse asyncio
Modèle dans Django
Formulaire à Django
Implémentation RNN en python
Traitement de fichiers en Python
Traitement multithread en python
Implémentation ValueObject en Python
Implémentation du menu déroulant dans Django
Notes de céleri sur Django
Traitement de texte avec Python
Modifications du modèle dans Django
Implémentation SVM en python