Node.js, Socket.io, Redis pub / sub grand volume, difficultés de latence faibles

Lorsque vous associez socket.io/node.js et redis pub / sub dans le but de créer un système de diffusion Web en temps réel piloté par des événements serveur pouvant gérer plusieurs transports, il semble y avoir trois approches:

  1. ‘createClient’ est une connexion redis et s’abonne aux canaux. Sur la connexion client socket.io, connectez le client dans une salle socket.io. Dans l’événement redis.on (“message”, …), appelez io.sockets.in (room) .emit (“event”, data) à dissortingbuer à tous les clients de la salle concernée. Comme Comment réutiliser la connexion redis dans socket.io?

  2. ‘createClient’ une connexion redis. Sur la connexion client socket.io, joignez le client dans une salle socket.io et abonnez-vous aux canaux redis pertinents. Inclure redis.on (“message”, …) dans la fermeture de la connexion client et à la réception de l’appel du message client.emit (“événement”, données) pour déclencher l’événement sur le client spécifique. Comme la réponse dans Exemples d’utilisation de RedisStore dans socket.io

  3. Utilisez le RedisStore cuit dans socket.io et «broadcast» à partir du seul canal «dispatch» dans Redis en suivant le protocole socketio-spec.

Le numéro 1 permet de gérer le sous-ensemble Redis et l’événement associé une fois pour tous les clients. Le numéro 2 offre un raccordement plus direct au pub / sous-redis. Le numéro 3 est plus simple, mais offre peu de contrôle sur les événements de messagerie.

Cependant, lors de mes tests, tous présentent des performances inattendues avec plus d’un client connecté. Les événements serveur en question sont les 1000 messages publiés sur un canal redis le plus rapidement possible, pour être dissortingbués le plus rapidement possible. Les performances sont mesurées par des temporisations sur les clients connectés (socket.io-client qui enregistre les horodatages dans une liste Redis pour parsing).

Ce que je suppose, c’est que, dans l’option 1, le serveur reçoit le message, puis l’écrit séquentiellement sur tous les clients connectés. Dans l’option 2, le serveur reçoit chaque message plusieurs fois (une fois par abonnement client) et l’écrit sur le client concerné. Dans les deux cas, le serveur n’atteint pas le deuxième événement de message jusqu’à ce qu’il soit communiqué à tous les clients connectés. Une situation clairement exacerbée par la concurrence croissante.

Cela semble en contradiction avec la sagesse perçue des capacités des stacks. Je veux croire, mais je me bats.

Ce scénario (dissortingbution à faible latence de gros volumes de messages) n’est-il pas une option pour ces outils (encore?) Ou est-ce que je me trompe?

Je pensais que c’était une question raisonnable et que je l’avais brièvement recherchée il y a quelques temps. J’ai passé un peu de temps à chercher des exemples que vous pourriez être en mesure de prendre des conseils utiles.

Exemples

J’aime commencer par des exemples simples:

  • lumière im code échantillon
  • Node.js + Redis Pub / Sub + démo socket.io

L’échantillon de lumière est une seule page (notez que vous voudrez remplacer redis-node-client par quelque chose comme node_redis par Matt Ranney:

/* * Mclarens Bar: Redis based Instant Messaging * Nikhil Marathe - 22/04/2010 * A simple example of an IM client implemented using * Redis PUB/SUB commands so that all the communication * is offloaded to Redis, and the node.js code only * handles command interpretation,presentation and subscribing. * * Requires redis-node-client and a recent version of Redis * http://code.google.com/p/redis * http://github.com/fictorial/redis-node-client * * Start the server then telnet to port 8000 * Register with NICK , use WHO to see others * Use TALKTO  to initiate a chat. Send a message * using MSG  . Note its important to do a * TALKTO so that both sides are listening. Use STOP  * to stop talking to someone, and QUIT to exit. * * This code is in the public domain. */ var redis = require('./redis-node-client/lib/redis-client'); var sys = require('sys'); var net = require('net'); var server = net.createServer(function(stream) { var sub; // redis connection var pub; var registered = false; var nick = ""; function channel(a,b) { return [a,b].sort().join(':'); } function shareTable(other) { sys.debug(nick + ": Subscribing to "+channel(nick,other)); sub.subscribeTo(channel(nick,other), function(channel, message) { var str = message.toSsortingng(); var sender = str.slice(0, str.indexOf(':')); if( sender != nick ) stream.write("[" + sender + "] " + str.substr(str.indexOf(':')+1) + "\n"); }); } function leaveTable(other) { sub.unsubscribeFrom(channel(nick,other), function(err) { stream.write("Stopped talking to " + other+ "\n"); }); } stream.addListener("connect", function() { sub = redis.createClient(); pub = redis.createClient(); }); stream.addListener("data", function(data) { if( !registered ) { var msg = data.toSsortingng().match(/^NICK (\w*)/); if(msg) { stream.write("SERVER: Hi " + msg[1] + "\n"); pub.sadd('mclarens:inside', msg[1], function(err) { if(err) { stream.end(); } registered = true; nick = msg[1]; // server messages sub.subscribeTo( nick + ":info", function(nick, message) { var m = message.toSsortingng().split(' '); var cmd = m[0]; var who = m[1]; if( cmd == "start" ) { stream.write( who + " is now talking to you\n"); shareTable(who); } else if( cmd == "stop" ) { stream.write( who + " stopped talking to you\n"); leaveTable(who); } }); }); } else { stream.write("Please register with NICK \n"); } return; } var fragments = data.toSsortingng().replace('\r\n', '').split(' '); switch(fragments[0]) { case 'TALKTO': pub.publish(fragments[1]+":info", "start " + nick, function(a,b) { }); shareTable(fragments[1]); break; case 'MSG': pub.publish(channel(nick, fragments[1]), nick + ':' +fragments.slice(2).join(' '), function(err, reply) { if(err) { stream.write("ERROR!"); } }); break; case 'WHO': pub.smembers('mclarens:inside', function(err, users) { stream.write("Online:\n" + users.join('\n') + "\n"); }); break; case 'STOP': leaveTable(fragments[1]); pub.publish(fragments[1]+":info", "stop " + nick, function() {}); break; case 'QUIT': stream.end(); break; } }); stream.addListener("end", function() { pub.publish(nick, nick + " is offline"); pub.srem('mclarens:inside', nick, function(err) { if(err) { sys.debug("Could not remove client"); } }); }); }); server.listen(8000, "localhost"); 

Documents

Il existe une tonne de documentation, et les API changent rapidement sur ce type de stack, vous devrez donc peser la pertinence temporelle de chaque document.

  • stream d’activité des noeuds
  • exemple de fonderie de nuage
  • comment faire le noeud redis pubsub
  • latence redis
  • redis cookbook Utilisation de Pub / Sub pour la communication asynchrone
  • Conseils génériques de linkedin
  • nœuds redis liaisons
  • question google groups nodejs

questions connexes

Juste quelques questions liées, il s’agit d’un sujet d’actualité sur la stack:

  • Redis pub / sub pour serveur de discussion dans node.js
  • Comment concevoir redis pub / sub pour un système de messagerie instantanée?

Conseils notables (ymmv)

Désactivez ou optimisez le regroupement de sockets, utilisez des liaisons efficaces, surveillez la latence et assurez-vous de ne pas dupliquer le travail (c.-à-d. Pas besoin de publier deux fois sur tous les écouteurs).