35 votes

Concurrence dans les racks - rack.multithread, async.callback, ou les deux ?

J'essaie de bien comprendre les options de traitement des requêtes simultanées dans Rack. J'ai utilisé async_sinatra pour construire une application à longues requêtes, et j'expérimente maintenant avec Rack bare-metal en utilisant throw :async et/ou le drapeau de Thin --threaded. Je suis à l'aise avec le sujet, mais il y a certaines choses que je n'arrive pas à comprendre. (Non, je ne confonds pas concurrence et parallélisme, et oui, je comprends les limitations imposées par la GIL).

Q1. Mes tests indiquent que thin --threaded (c'est-à-dire rack.multithread=true ) exécute les requêtes simultanément dans des threads séparés (je suppose qu'il utilise EM), ce qui signifie qu'une requête A longuement exécutée ne bloquera pas la requête B (IO mis à part). Cela signifie que mon application n'a pas besoin d'un codage spécial (par exemple, des callbacks) pour obtenir la simultanéité (encore une fois, en ignorant les appels bloquants à la base de données, les E/S, etc.) C'est ce que je crois avoir observé - est-ce exact ?

Q2. Il existe un autre moyen, plus souvent discuté, de réaliser la concurrence, qui consiste à EventMachine.defer y throw :async . A proprement parler, les demandes sont no gérés à l'aide de threads. Elles sont traitées en série, mais transmettent le gros du travail et un callback à EventMachine, qui utilise async.callback pour envoyer une réponse à un moment ultérieur. Après que la demande A a déchargé son travail sur EM.defer, la demande B est lancée. Est-ce correct ?

Q3. En supposant que les réponses ci-dessus soient plus ou moins correctes, Y a-t-il un avantage particulier à une méthode plutôt qu'à une autre ? Évidemment --threaded ressemble à une balle magique. Y a-t-il des inconvénients ? Si non, pourquoi tout le monde parle de async_sinatra / throw :async / async.callback ? Peut-être que la première option est "Je veux rendre mon application Rails un peu plus rapide en cas de forte charge" et que la seconde est mieux adaptée aux applications comportant de nombreuses requêtes de longue durée ? Ou peut-être l'échelle est-elle un facteur ? Je ne fais que supposer.

J'utilise Thin 1.2.11 sur MRI Ruby 1.9.2. (Pour info, je dois utiliser l'extension --no-epoll car il y a un problème de longue date, prétendument résolu, mais qui ne l'est pas vraiment. avec l'utilisation d'epoll et de Ruby 1.9.2 par EventMachine. Cela n'a rien à voir avec le sujet, mais toute idée est la bienvenue).

24voto

Konstantin Haase Points 12089

Remarque : j'utilise Thin comme synonyme de tous les serveurs web qui mettent en œuvre l'extension asynchrone Rack (c'est-à-dire Rainbows !, Ebb, les futures versions de Puma, ...).

Q1. Correct. Il va envelopper la génération de la réponse (alias call ) en EventMachine.defer { ... } ce qui amènera EventMachine à le pousser sur son pool de threads intégré.

Q2. Utilisation de async.callback en collaboration avec EM.defer n'a en fait pas beaucoup de sens, car elle utiliserait aussi le pool de threads, ce qui aboutirait à une construction similaire à celle décrite dans la Q1. Utilisation de async.callback a du sens lorsque l'on n'utilise que les bibliothèques d'eventmachine pour l'IO. Thin enverra la réponse au client une seule fois. env['async.callback'] est appelé avec une réponse normale du Rack comme argument.

Si le corps est un EM::Deferrable Thin ne fermera pas la connexion tant que ce différé n'aura pas abouti. Un secret assez bien gardé : si vous voulez plus qu'une simple interrogation longue (c'est-à-dire garder la connexion ouverte après l'envoi d'une réponse partielle), vous pouvez également renvoyer un message de type EM::Deferrable comme objet du corps directement sans avoir à utiliser throw :async ou un code d'état de -1 .

Q3. Vous avez deviné juste. Service fileté pourrait améliorer la charge d'une application Rack par ailleurs inchangée. Je constate une amélioration de 20% pour les applications Sinatra simples sur ma machine avec Ruby 1.9.3, et encore plus lorsqu'elles sont exécutées sur Rubinius ou JRuby, où tous les cœurs peuvent être utilisés. La deuxième approche est utile si vous écrivez votre application de manière événementielle.

Vous pouvez ajouter beaucoup de magie et de bidouillages à Rack pour qu'une application non événementielle utilise ces mécanismes (voir em-synchrony ou sinatra-synchrony), mais vous vous retrouverez dans l'enfer du débogage et des dépendances.

L'approche asynchrone prend tout son sens avec les applications qui ont tendance à être mieux résolues avec une approche événementielle, comme par exemple un chat en ligne . Cependant, je ne recommande pas l'utilisation de l'approche threadée pour la mise en œuvre du polling long, car chaque connexion de polling bloquera un thread. Cela vous laissera avec soit une tonne de threads, soit des connexions que vous ne pourrez pas gérer. Le pool de threads d'EM a une taille de 20 threads par défaut, ce qui vous limite à 20 connexions en attente par processus.

Vous pourriez utiliser un serveur qui crée un nouveau thread pour chaque connexion entrante, mais la création de threads est coûteuse (sauf sur MacRuby, mais je n'utiliserais pas MacRuby dans une application de production). Exemples serv y net-http-server . Idéalement, ce que vous voulez, c'est une correspondance n:m des demandes et des fils. Mais il n'existe aucun serveur qui offre cela.

Si vous voulez en savoir plus sur le sujet : J'ai fait une présentation à ce sujet à Rocky Mountain Ruby (et à une tonne d'autres conférences). Un enregistrement vidéo peut être trouvé sur les confréries .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X