Un des gros inconvénients du modèle de Nodejs est d'avoir un seul fil d'exécution en collaboratif. Cela veut dire que si ce fil d'exécution est bloqué pendant 100ms pour calculer le hash d'un mot de passe avec une fonction moderne de hashage comme scrypt, il n'y a aucune autre requête HTTP qui va être traitée pendant ce temps et la latence va augmenter de 100ms pour toutes les requêtes de ce processus.
Ce serait effectivement une manière assez plus maladroite de faire! ;) Dans ce paradigme on n'attend que les "threads" collaborent entre eux: de temps en temps il faut donc rendre la main à la boucle. Plutôt qu'un long discours, un petit exemple:
(À tout hasard je précise que je suis loin d'être un spécialiste ou un amoureux de JavaScript, moi mon truc c'est plutôt OCaml, Lisp, SH et TeX. Donc mon code est très certainement assez médiocre. ;) )
Ma fonction combine déduit un hash des valeurs de a1 et a2, et j'utilise un "schéma de Fibonacci" pour faire une boucle, qui toutes les 20 itérations repasse la main à l'ordonnanceur pour collaborer un peu avec ses petits copains: il suffit de se retenir de faire le calcul et de demander à la boucle de continuer quand bon lui semble.
Voici un code complet qui met ça dans un serveur HTTP
consthttp=require('http');constprocess=require('process');constcrypto=require('crypto');consturl=require('url');constserver_port=8080;constserver_iterations=400000;constfibo_initialize_a1="I don't like secrets.";constfibo_initialize_a2="God is my co-pilot.";functioncombine(a1,a2){returncrypto.createHmac('sha256',a1).update(a2).digest('hex');}functionfiboLoop(n,k,a1,a2,callback){varcollaborate_p=(k%20===0);if(n==k){callback(a2);}else{if(collaborate_p){setTimeout(()=>{fiboLoop(n,k+1,a2,combine(a1,a2),callback);},1);}else{fiboLoop(n,k+1,a2,combine(a1,a2),callback);}}}functionfibo(n,callback){fiboLoop(n,0,fibo_initialize_a1,fibo_initialize_a2,callback);}functionsafeParseInt(text,_defaultValue){varparsedInt=parseInt(text);vardefaultValue=_defaultValue||0;returnisNaN(parsedInt)?defaultValue:parsedInt;};http.createServer(function(req,res){varparts=url.parse(req.url,true);varquery=parts.query;variterations=('n'inquery)?safeParseInt(query['n']):server_iterations;console.log('Receive fibo '+iterations);fibo(iterations,(ax)=>{res.writeHead(200,{'Content-Type':'text/plain'});res.write('Hello World! '+ax);res.end();console.log('Done fibo '+iterations);});}).listen(server_port);
On peut tester avec quelques commandes (while true; do curl 'localhost:8080?n=10'; done)(while true; do curl 'localhost:8080?n=100'; done) et (while true; do curl 'localhost:8080?n=10000'; done) qui tournent dans des terminaux.
On a également la distinction entre les threads dédiées aux appels système et les autre threads dédiés aux traitements. Le nombre de threads dédiés aux traitements est la variable d'ajustement pour la montée en puissance, il est limité par la variable d'environnement GOMAXPROCS.
C'est sympa cette distinction. En pratique ça se passe comment? Il faut annoter ses routines ou bien le compilateur s'en sort tout seul? Aussi c'est un peu bizarre de penser qu'une fonction change de catégorie si on la "pepper-printf" pour déboguer.
alors que pour Nodejs, ça demande souvent encore un peu de boulot de passer d'un seul à plusieurs processus : écrire le petit bout de code pour cluster, faire attention aux variables globales qui ne le sont plus (cache, pools de connexions à la base de données), etc.
Si on cherche à faire facilement du redimensionnement de capacité, la bonne stratégie dépend du contexte. Si par exemple on travaille avec des ressources de calcul élastiques avec des instances de 2-4 cœurs qu'on ajuste à la capacité demandée on peut se passer complètement de la couche cluster et compter sur un reverse-proxy type haproxy ou bien par exemple le répartiteur de charge de docker ou bien le consul de hashicorp par exemple – ou tout autre composant qui va répartir le flux sur plusieurs processus qui tournent sur des serveurs différents. Et même si on a une machine grassouillette avec 192 cœurs dont on veut tirer parti, ce n'est pas forcément aberrant de procéder de la sorte aussi.
[^] # Re: Petit joueur
Posté par Michaël (site web personnel) . En réponse au journal Des vieilles bases d'unix à la hype reactive actuelle. Évalué à 4.
Ce serait effectivement une manière assez plus maladroite de faire! ;) Dans ce paradigme on n'attend que les "threads" collaborent entre eux: de temps en temps il faut donc rendre la main à la boucle. Plutôt qu'un long discours, un petit exemple:
(À tout hasard je précise que je suis loin d'être un spécialiste ou un amoureux de JavaScript, moi mon truc c'est plutôt OCaml, Lisp, SH et TeX. Donc mon code est très certainement assez médiocre. ;) )
Ma fonction
combinedéduit un hash des valeurs de a1 et a2, et j'utilise un "schéma de Fibonacci" pour faire une boucle, qui toutes les 20 itérations repasse la main à l'ordonnanceur pour collaborer un peu avec ses petits copains: il suffit de se retenir de faire le calcul et de demander à la boucle de continuer quand bon lui semble.Voici un code complet qui met ça dans un serveur HTTP
On peut tester avec quelques commandes
(while true; do curl 'localhost:8080?n=10'; done)(while true; do curl 'localhost:8080?n=100'; done)et(while true; do curl 'localhost:8080?n=10000'; done)qui tournent dans des terminaux.C'est sympa cette distinction. En pratique ça se passe comment? Il faut annoter ses routines ou bien le compilateur s'en sort tout seul? Aussi c'est un peu bizarre de penser qu'une fonction change de catégorie si on la "pepper-printf" pour déboguer.
Si on cherche à faire facilement du redimensionnement de capacité, la bonne stratégie dépend du contexte. Si par exemple on travaille avec des ressources de calcul élastiques avec des instances de 2-4 cœurs qu'on ajuste à la capacité demandée on peut se passer complètement de la couche cluster et compter sur un reverse-proxy type haproxy ou bien par exemple le répartiteur de charge de docker ou bien le consul de hashicorp par exemple – ou tout autre composant qui va répartir le flux sur plusieurs processus qui tournent sur des serveurs différents. Et même si on a une machine grassouillette avec 192 cœurs dont on veut tirer parti, ce n'est pas forcément aberrant de procéder de la sorte aussi.