J'ai un projet LOC 10K écrit à Django avec pas mal de céleri ( RabbitMQ ) pour l'asynchronicité et les travaux en arrière-plan si nécessaire, et je suis arrivé à la conclusion que certaines parties du système gagneraient à être réécrites dans autre chose que Django pour une meilleure concurrence . Les raisons incluent:
- Manipulation des signaux et objets mutables. Surtout quand un signal en déclenche un autre, les gérer dans Django à l'aide de l' ORM peut être surprenant lorsque les instances changent ou disparaissent. Je veux utiliser une approche de messagerie où les données transmises ne changent pas dans un gestionnaire ( l' approche de copie sur écriture de Clojure semble agréable, si je comprends bien).
- Certaines parties du système ne sont pas basées sur le Web et nécessitent un meilleur support pour effectuer des tâches simultanément. Par exemple, le système lit les balises NFC et lorsque l'une est lue, une LED s'allume pendant quelques secondes (tâche Céleri), un son est joué (autre tâche Céleri) et la base de données est interrogée (autre tâche). Ceci est implémenté comme une commande de gestion Django, mais Django et son ORM étant synchrone par nature et partageant la mémoire est limitant (nous pensons ajouter plus de lecteurs NFC, et je ne pense pas que l'approche Django + Celery fonctionnera plus longtemps, J'aimerais voir de meilleures capacités de transmission de messages).
Quels sont les avantages et les inconvénients d'utiliser quelque chose comme Twisted ou Tornado par rapport à une langue comme Erlang ou Clojure ? Je m'intéresse aux avantages et aux inconvénients pratiques.
Comment en êtes-vous arrivé à la conclusion que certaines parties du système s'en tireraient mieux dans une autre langue? Souffrez-vous de problèmes de performances? Quelle est la gravité de ces problèmes? Si cela peut être plus rapide, est-il essentiel qu'il soit plus rapide?
Exemple 1: Django au travail en dehors d'une requête HTTP:
- Une balise NFC est lue.
- La base de données (et éventuellement LDAP) est interrogée, et nous voulons faire quelque chose lorsque les données deviennent disponibles (lumière rouge ou verte, émettre un son). Cela bloque l'utilisation de l'ORM Django, mais tant qu'il y a des travailleurs Celery disponibles, cela n'a pas d'importance. Peut être un problème avec plus de stations.
Exemple 2: «passage de message» à l'aide de signaux Django:
- Un
post_delete
événement est géré, d'autres objets peuvent être modifiés ou supprimés à cause de cela. - À la fin, les notifications doivent être envoyées aux utilisateurs. Ici, ce serait bien si les arguments passés au gestionnaire de notifications étaient des copies d'objets supprimés ou à supprimer et garantis de ne pas changer dans le gestionnaire. (Cela pourrait être fait manuellement simplement en ne passant pas d'objets gérés par l'ORM aux gestionnaires, bien sûr.)
la source
Réponses:
Pensées d'ouverture
Comment en êtes-vous arrivé à la conclusion que certaines parties du système s'en tireraient mieux dans une autre langue? Souffrez-vous de problèmes de performances? Quelle est la gravité de ces problèmes? Si cela peut être plus rapide, est-il essentiel qu'il soit plus rapide?
Asynchronie monofil
Il existe plusieurs questions et autres ressources Web qui traitent déjà des différences, des avantages et des inconvénients de l'asynchronie à un seul thread par rapport à la concurrence à plusieurs threads. Il est intéressant de lire comment le modèle asynchrone à thread unique de Node.js fonctionne lorsque les E / S sont le principal goulot d'étranglement, et de nombreuses demandes sont traitées en même temps.
Twisted, Tornado et d'autres modèles asynchrones font une excellente utilisation d'un seul thread. Étant donné qu'un grand nombre de programmes Web comportent de nombreuses E / S (réseau, base de données, etc.), le temps passé à attendre les appels distants s'additionne considérablement. C'est du temps qui pourrait être consacré à d'autres tâches, comme lancer d'autres appels de base de données, afficher des pages et générer des données. L'utilisation de ce thread unique est extrêmement élevée.
L'un des plus grands avantages de l'asynchronie à un seul thread est qu'il utilise beaucoup moins de mémoire. Dans l'exécution multi-thread, chaque thread nécessite une certaine quantité de mémoire réservée. À mesure que le nombre de threads augmente, la quantité de mémoire requise pour que les threads existent également. Puisque la mémoire est finie, cela signifie qu'il y a des limites sur le nombre de threads qui peuvent être créés à tout moment.
Exemple
Dans le cas d'un serveur Web, faites comme si chaque requête avait son propre thread. Supposons que 1 Mo de mémoire soit requis pour chaque thread et que le serveur Web dispose de 2 Go de RAM. Ce serveur Web serait capable de traiter (environ) 2000 requêtes à tout moment avant qu'il n'y ait tout simplement plus assez de mémoire pour traiter plus.
Si votre charge est considérablement plus élevée que cela, les demandes vont prendre très longtemps (en attendant que les anciennes demandes se terminent), ou vous devrez jeter plus de serveurs dans le cluster pour augmenter le nombre de demandes simultanées possibles .
Accès simultané multi-thread
La concurrence multithread repose à la place sur l'exécution de plusieurs tâches en même temps. Cela signifie que si un thread est bloqué en attente d'un appel de base de données à renvoyer, d'autres requêtes peuvent être traitées en même temps. L'utilisation des threads est plus faible, mais le nombre de threads en cours d'exécution est beaucoup plus important.
Le code multi-thread est également beaucoup plus difficile à raisonner. Il y a des problèmes de verrouillage, de synchronisation et d'autres problèmes de concurrence amusants. L'asynchronie monofil ne souffre pas des mêmes problèmes.
Cependant, le code multi-thread est beaucoup plus performant pour les tâches gourmandes en CPU . S'il n'y a aucune possibilité pour un thread de «céder» - comme un appel réseau qui se bloquerait normalement - un modèle à un seul thread n'aura tout simplement aucune concurrence.
Les deux peuvent coexister
Il y a bien sûr un chevauchement entre les deux; ils ne s'excluent pas mutuellement. Par exemple, le code multi-thread peut être écrit de manière non bloquante, pour mieux utiliser chaque thread.
Le résultat final
Il y a beaucoup d'autres questions à considérer, mais j'aime penser aux deux comme ceci:
Dans votre cas particulier, vous devez déterminer quel type de travail asynchrone est en cours d'exécution et à quelle fréquence ces tâches se produisent.
Il n'y a pas de réponse simple. Vous devez considérer quels sont vos cas d'utilisation et les concevoir en conséquence. Parfois, un modèle asynchrone à un seul thread est préférable. D'autres fois, l'utilisation d'un certain nombre de threads pour réaliser un traitement parallèle massif est nécessaire.
autres considérations
Il y a d'autres problèmes que vous devez également prendre en compte, plutôt que simplement le modèle de concurrence que vous choisissez. Connaissez-vous Erlang ou Clojure? Pensez-vous que vous seriez capable d'écrire du code multithread sécurisé dans l'une de ces langues afin d'améliorer les performances de votre application? Cela va-t-il prendre beaucoup de temps pour se mettre au courant dans l'une de ces langues, et la langue que vous apprenez vous sera-t-elle avantageuse à l'avenir?
Qu'en est-il des difficultés liées à la communication entre ces deux systèmes? Sera-t-il trop complexe de maintenir deux systèmes distincts en parallèle? Comment le système Erlang recevra-t-il les tâches de Django? Comment Erlang communiquera-t-il ces résultats à Django? Les performances sont-elles suffisamment importantes pour que la complexité supplémentaire en vaille la peine?
Dernières pensées
J'ai toujours trouvé que Django était assez rapide, et il est utilisé par certains sites très fréquentés. Vous pouvez effectuer plusieurs optimisations de performances pour augmenter le nombre de demandes simultanées et le temps de réponse. Certes, je n'ai encore rien fait avec Celery, donc les optimisations de performances habituelles ne résoudront probablement pas les problèmes que vous pourriez avoir avec ces tâches asynchrones.
Bien sûr, il y a toujours la suggestion de jeter plus de matériel sur le problème. Le coût d'approvisionnement d'un nouveau serveur est-il moins cher que le coût de développement et de maintenance d'un sous-système entièrement nouveau?
J'ai posé beaucoup trop de questions à ce stade, mais c'était mon intention. La réponse ne sera pas facile sans analyse et sans plus de détails. Cependant, être capable d'analyser les problèmes revient à connaître les questions à poser… alors j'espère que j'ai aidé sur ce front.
Mon intuition dit qu'une réécriture dans une autre langue n'est pas nécessaire. La complexité et le coût seront probablement trop importants.
modifier
Réponse au suivi
Votre suivi présente des cas d'utilisation très intéressants.
1. Django travaillant en dehors des requêtes HTTP
Votre premier exemple consistait à lire des balises NFC, puis à interroger la base de données. Je ne pense pas que l'écriture de cette partie dans une autre langue vous sera utile, simplement parce que l'interrogation de la base de données ou d'un serveur LDAP va être liée par les E / S du réseau (et potentiellement les performances de la base de données). En revanche, le nombre de demandes simultanées sera lié par le serveur lui-même, car chaque commande de gestion sera exécutée comme son propre processus. Il y aura un temps de configuration et de démontage qui aura un impact sur les performances, car vous n'envoyez pas de messages à un processus déjà en cours. Cependant, vous pourrez envoyer plusieurs demandes en même temps, car chacune sera un processus isolé.
Pour ce cas, je vois deux pistes sur lesquelles vous pouvez enquêter:
'OPTIONS': {'threaded':True}
.) Il peut y avoir des options de configuration similaires au niveau de la base de données ou au niveau de Django que vous pouvez modifier pour votre propre base de données. Quelle que soit la langue dans laquelle vous écrivez vos requêtes de base de données, vous devrez attendre que ces données reviennent avant de pouvoir allumer les LED. Les performances du code d'interrogation peuvent faire une différence cependant, et l'ORM de Django n'est pas rapide comme l'éclair ( mais , généralement assez rapide).Je ne sais pas quel serveur Web vous utilisez pour Django.
mod_wsgi
pour Apache vous permet de configurer le nombre de processus et de threads dans les processus que le service demande. Assurez-vous de modifier la configuration appropriée de votre serveur Web pour optimiser le nombre de demandes réparables.2. «Passage de messages» avec des signaux Django
Votre deuxième cas d'utilisation est également assez intéressant; Je ne suis pas sûr d'avoir les réponses à cela. Si vous supprimez des instances de modèle et souhaitez les utiliser ultérieurement, il peut être possible de les sérialiser
JSON.dumps
puis de les désérialiserJSON.loads
. Il sera impossible de recréer complètement le graphique d'objet plus tard (interrogation de modèles liés), car les champs associés sont chargés paresseusement à partir de la base de données et ce lien n'existera plus.L'autre option serait de marquer en quelque sorte un objet à supprimer, et de le supprimer uniquement à la fin du cycle de demande / réponse (après que tous les signaux ont été traités). Cela peut nécessiter un signal personnalisé pour l'implémenter, plutôt que de s'appuyer sur
post_delete
.la source
J'ai fait un développement hautement évolutif très sophistiqué pour un grand FAI américain . Nous avons fait de sérieux numéros de tranasaction en utilisant un serveur Twisted , et c'était un cauchemar de complexité pour obtenir Python / Twisted pour évoluer sur tout ce qui était lié au CPU . La liaison avec les E / S n'est pas un problème, mais la liaison avec le processeur était impossible. Nous pouvions assembler rapidement les systèmes, mais les faire évoluer vers des millions d'utilisateurs simultanés était un cauchemar de configuration et de complexité s'ils étaient liés par le processeur.
J'ai écrit un billet de blog à ce sujet, Python / Twisted VS Erlang / OTP .
TLDR; Erlang a gagné.
la source
Problèmes pratiques avec Twisted (que j'aime et que j'utilise depuis environ cinq ans):
J'ai fait un peu de travail en utilisant Node.js avec CoffeeScript et si les performances simultanées sont votre préoccupation, cela peut valoir le coup.
Avez-vous envisagé d'exécuter plusieurs instances de Django avec un arrangement pour répartir les clients entre les instances?
la source
Je vous suggère ce qui suit avant d'envisager de passer à une autre langue.
select
), ce qui est bon pour les E / S.Je n'utiliserais pas le filetage en Python une fois que l'application a la priorité dans les performances. Je prendrais l'option ci-dessus, qui peut résoudre de nombreux problèmes comme la réutilisation de logiciels, la connectivité avec Django , les performances, la facilité de développement, etc.
la source