sql >> データベース >  >> NoSQL >> Redis

クラスターを使用してSocket.IOを複数のNode.jsプロセスにスケーリングする

    編集: Socket.IO 1.0以降では、複数のRedisクライアントでストアを設定するのではなく、よりシンプルなRedisアダプターモジュールを使用できるようになりました。

    var io = require('socket.io')(3000);
    var redis = require('socket.io-redis');
    io.adapter(redis({ host: 'localhost', port: 6379 }));
    

    以下に示す例は、次のようになります。

    var cluster = require('cluster');
    var os = require('os');
    
    if (cluster.isMaster) {
      // we create a HTTP server, but we do not use listen
      // that way, we have a socket.io server that doesn't accept connections
      var server = require('http').createServer();
      var io = require('socket.io').listen(server);
      var redis = require('socket.io-redis');
    
      io.adapter(redis({ host: 'localhost', port: 6379 }));
    
      setInterval(function() {
        // all workers will receive this in Redis, and emit
        io.emit('data', 'payload');
      }, 1000);
    
      for (var i = 0; i < os.cpus().length; i++) {
        cluster.fork();
      }
    
      cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
      }); 
    }
    
    if (cluster.isWorker) {
      var express = require('express');
      var app = express();
    
      var http = require('http');
      var server = http.createServer(app);
      var io = require('socket.io').listen(server);
      var redis = require('socket.io-redis');
    
      io.adapter(redis({ host: 'localhost', port: 6379 }));
      io.on('connection', function(socket) {
        socket.emit('data', 'connected to worker: ' + cluster.worker.id);
      });
    
      app.listen(80);
    }
    

    他のSocket.IOプロセスに公開する必要があるが、ソケット接続自体を受け入れないマスターノードがある場合は、socket.io-redisの代わりにsocket.io-emitterを使用してください。

    スケーリングに問題がある場合は、DEBUG=*を使用してNodeアプリケーションを実行してください 。 Socket.IOは、Redisアダプターのデバッグメッセージも出力するデバッグを実装するようになりました。出力例:

    socket.io:server initializing namespace / +0ms
    socket.io:server creating engine.io instance with opts {"path":"/socket.io"} +2ms
    socket.io:server attaching client serving req handler +2ms
    socket.io-parser encoding packet {"type":2,"data":["event","payload"],"nsp":"/"} +0ms
    socket.io-parser encoded {"type":2,"data":["event","payload"],"nsp":"/"} as 2["event","payload"] +1ms
    socket.io-redis ignore same uid +0ms
    

    マスタープロセスと子プロセスの両方が同じパーサーメッセージを表示する場合、アプリケーションは適切にスケーリングされています。

    単一のワーカーから放出している場合は、セットアップに問題はありません。あなたがしていることは、4人のワーカーすべてから送信されており、Redisのパブリッシュ/サブスクライブにより、メッセージは複製されませんが、アプリケーションに要求したとおりに4回書き込まれます。これがRedisの機能の簡単な図です:

    Client  <--  Worker 1 emit -->  Redis
    Client  <--  Worker 2  <----------|
    Client  <--  Worker 3  <----------|
    Client  <--  Worker 4  <----------|
    

    ご覧のとおり、ワーカーから放出すると、放出がRedisに公開され、Redisデータベースにサブスクライブしている他のワーカーからミラーリングされます。これは、同じインスタンスに接続された複数のソケットサーバーを使用できることも意味し、1つのサーバーでのemitは、接続されているすべてのサーバーで起動されます。

    クラスタでは、クライアントが接続すると、4つすべてではなく、4つのワーカーの1つに接続します。つまり、そのワーカーから送信したものはすべて、クライアントに1回だけ表示されます。そうです、アプリケーションはスケーリングしていますが、それを実行している方法では、4つのワーカーすべてから放出されており、Redisデータベースは、1つのワーカーで4回呼び出しているかのようにアプリケーションを作成しています。クライアントが実際に4つのソケットインスタンスすべてに接続している場合、クライアントは1秒間に4つではなく16のメッセージを受信します。

    ソケット処理のタイプは、使用するアプリケーションのタイプによって異なります。クライアントを個別に処理する場合は、接続イベントが1つのクライアントごとに1つのワーカーに対してのみ発生するため、問題はありません。グローバルな「ハートビート」が必要な場合は、マスタープロセスにソケットハンドラーを含めることができます。マスタープロセスが停止するとワーカーが停止するため、マスタープロセスの接続負荷を相殺し、子に接続を処理させる必要があります。次に例を示します:

    var cluster = require('cluster');
    var os = require('os');
    
    if (cluster.isMaster) {
      // we create a HTTP server, but we do not use listen
      // that way, we have a socket.io server that doesn't accept connections
      var server = require('http').createServer();
      var io = require('socket.io').listen(server);
    
      var RedisStore = require('socket.io/lib/stores/redis');
      var redis = require('socket.io/node_modules/redis');
    
      io.set('store', new RedisStore({
        redisPub: redis.createClient(),
        redisSub: redis.createClient(),
        redisClient: redis.createClient()
      }));
    
      setInterval(function() {
        // all workers will receive this in Redis, and emit
        io.sockets.emit('data', 'payload');
      }, 1000);
    
      for (var i = 0; i < os.cpus().length; i++) {
        cluster.fork();
      }
    
      cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
      }); 
    }
    
    if (cluster.isWorker) {
      var express = require('express');
      var app = express();
    
      var http = require('http');
      var server = http.createServer(app);
      var io = require('socket.io').listen(server);
    
      var RedisStore = require('socket.io/lib/stores/redis');
      var redis = require('socket.io/node_modules/redis');
    
      io.set('store', new RedisStore({
        redisPub: redis.createClient(),
        redisSub: redis.createClient(),
        redisClient: redis.createClient()
      }));
    
      io.sockets.on('connection', function(socket) {
        socket.emit('data', 'connected to worker: ' + cluster.worker.id);
      });
    
      app.listen(80);
    }
    

    この例では、5つのSocket.IOインスタンスがあり、1つはマスターで、4つは子です。マスターサーバーがlisten()を呼び出すことはありません したがって、そのプロセスに接続のオーバーヘッドはありません。ただし、マスタープロセスでemitを呼び出すと、Redisに公開され、4つのワーカープロセスがクライアントでemitを実行します。これにより、ワーカーへの接続負荷が相殺され、ワー​​カーが停止した場合、メインのアプリケーションロジックはマスターで変更されません。

    Redisを使用すると、名前空間やルーム内であっても、すべてのエミットは、他のワーカープロセスによって、そのプロセスからのエミットをトリガーしたかのように処理されることに注意してください。つまり、1つのRedisインスタンスを持つ2つのSocket.IOインスタンスがある場合、emit()を呼び出します。 最初のワーカーのソケットでは、データがクライアントに送信されますが、ワーカー2は、そのワーカーからの送信を呼び出した場合と同じように動作します。



    1. Hadoopでのラック認識とその利点

    2. MongoDB$notアグリゲーションパイプラインオペレーター

    3. mongodb c#BSONドキュメントの操作方法

    4. MongoDBノードドライバーがインスタンスプールを生成してエラーを破棄するのはなぜですか?