带有粘性会话的 Nodejs 集群

Posted

技术标签:

【中文标题】带有粘性会话的 Nodejs 集群【英文标题】:Nodejs Clustering with Sticky-Session 【发布时间】:2018-12-20 10:02:30 【问题描述】:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) 
  console.log(`Master $process.pid is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) 
    cluster.fork();
  

  cluster.on('exit', (worker, code, signal) => 
    console.log(`worker $worker.process.pid died`);
  );
 else 
  // Workers can share any TCP connection
  // In this case it is an HTTP server



  var sticky = require('sticky-session');
  var express = require('express');
  var app = express();

  app.get('/', function (req, res) 
      console.log('worker: ' + cluster.worker.id);
      res.send('Hello World!');
  );


  var server = http.createServer(app);
      sticky.listen(server,3000);

  console.log(`Worker $process.pid started`);

我查阅了有关 nodejs 集群和粘性会话的文档 以及关于此的另一个堆栈溢出答案

  var cluster = require('cluster');
  var http = require('http');
  var sticky = require('sticky-session');
  var express = require('express');
  var app = express();

  app.get('/', function (req, res) 
      console.log('worker: ' + cluster.worker.id);
      res.send('Hello World!');
  );


  var server = http.createServer(app);
      sticky.listen(server,3000);

如果上面的 sn-p 在没有分叉的情况下运行,它可以正常工作,但不会像上面集群示例中所示的那样工作,其中线程已启动但服务器从未初始化。

我读到有粘性集群的替代方案,有人可以就这个主题给出适当的权威答案,这对于寻找相同主题的人很有用,另一个主要问题是 app.locals 对象,它用于为应用程序实例存储变量,并且多个服务器实例的出现导致此中断,因为不同实例的值会有所不同,因此这种方法会导致一个大问题并且应用程序会中断。回答时请不要复制粘贴一些代码,请提供详细信息回答详细说明该方法的优点和缺点。

我不是在寻找仅限于使用 sticky-sessions nodejs 模块的答案,我欢迎使用处理器的所有内核并确保会话连续性的所有其他方法。

如果它涉及 RedisStore 或 MongoDb 存储,我想知道的是关于具有会话连续性的集群的 nodejs 应用程序的标准方法

https://github.com/indutny/sticky-session

https://nodejs.org/api/cluster.html

https://***.com/a/37769107/3127499

【问题讨论】:

我真的不知道答案,但尝试移动 var sticky = require('sticky-session');到代码的开头,因此它们都使用相同的会话。 不,它没有帮助 【参考方案1】:

您的代码中有一个小问题。 “sticky-session”模块已经使用了node.js的“cluster”模块。你不需要“fork()”,因为sticky- session 已经为您完成了。让我们看看如何:

var cluster = require('cluster'); // Only required if you want the worker id
var sticky = require('sticky-session');

var server = require('http').createServer(function(req, res) 
  res.end('worker: ' + cluster.worker.id);
);
sticky.listen(server, 3000);

调用sticky.listen() 已经为你生成了worker。参见下面的listen() 实现

 function listen(server, port, options) 
  if (!options)
    options = ;

  if (cluster.isMaster) 
    var workerCount = options.workers || os.cpus().length;

    var master = new Master(workerCount, options.env);
    master.listen(port);
    master.once('listening', function() 
      server.emit('listening');
    );
    return false;
  
  return true;

这一行 var master = new Master(workerCount, options.env) 负责生成工人。 请参阅下面的 Master() 实现:

function Master(workerCount, env) 
  net.Server.call(this, 
    pauseOnConnect: true
  , this.balance);

  this.env = env || ;

  this.seed = (Math.random() * 0xffffffff) | 0;
  this.workers = [];

  debug('master seed=%d', this.seed);

  this.once('listening', function() 
    debug('master listening on %j', this.address());

    for (var i = 0; i < workerCount; i++)
      // spawning workers
      this.spawnWorker();
  );

确实,当您调用sticky.listen(server,port) 时,您实际上是在调用cluster.fork()。因此您不应该再次显式调用fork()。 现在您的代码应该如下所示:

var cluster = require('cluster'); // Only required if you want the worker id
var sticky = require('sticky-session');

var server = require('http').createServer(function(req, res) 
  res.end('worker: ' + cluster.worker.id);
);

//sticky.listen() will return false if Master
if (!sticky.listen(server, 3000))  
  // Master code
  server.once('listening', function() 
    console.log('server started on 3000 port');
  );
 else 
  // Worker code

要记住的重要一点是,生成的工作人员将拥有自己的 EVENTLOOP内存,因此资源不会在彼此之间共享。 您可以使用“REDIS”或其他 npm 模块(例如“memored”)在不同的工作人员之间共享资源。

希望这能解决你的两个问题。

【讨论】:

vasi 我正在寻找一个可行的实现,我在发布示例程序后查看了代码并了解了您在此处提到的内容。他的问题更多是关于会话连续性,即由派生的工作人员处理的第一个请求如何确保第二个后续请求由同一个工作人员处理我认为粘性会话声称可以确保。我没有坚持使用粘性会话模块。我想知道如何通过集群确保会话连续性。 chat.***.com/rooms/176317/nodejs-clustering 来聊天室会让你填满这个【参考方案2】:

如果我正确理解您的问题,您正在处理内存数据存储或会话存储。这是多节点或集群中基于会话的身份验证中的已知问题之一。假设您对节点 A 进行了调用并获得了名为 sessionA 的会话,但在下一次调用中,您对节点 B 进行了调用。节点 B 对 sessionA 一无所知。人们试图通过使用粘性会话来解决这个问题,但这还不够。好的做法是使用替代方法,例如 JWT 或 oAuth2。我更喜欢 JWT 进行服务到服务的通信。 JWT 不存储任何东西并且是无状态的。由于 REST 也是无状态的,因此它与 REST 配合得非常出色。这里https://www.rfc-editor.org/rfc/rfc7519 是JWT 实现的规范。如果您需要某种刷新令牌,那么您需要考虑存储。存储可以是 REDIS、MongoDB 或任何其他基于 SQL 的数据库。有关 nodejs 中 JWT 的进一步说明:

https://jwt.io/

https://jwt.io/introduction/

https://www.npmjs.com/package/jsonwebtoken

https://cloud.google.com/iot/docs/how-tos/credentials/jwts#iot-core-jwt-refresh-nodejs

【讨论】:

【参考方案3】:

我认为您将 sticky sessionshared memory store 混淆了。

让我试着帮忙:

Sticky-sessions 模块 正在使用其 IP 地址平衡请求。因此客户端将始终连接到同一个工作服务器,并且 socket.io 将按预期工作,但在多个 进程!

实施粘性会话意味着您现在有多个节点接受连接。但是,它保证这些节点将共享相同的内存,因为每个工作人员都有自己的事件循环和内部内存状态。

换句话说,一个节点正在处理的数据可能对其他工作节点不可用,这解释了您指出的问题。

...另一个主要问题是 app.locals 对象 用于存储应用程序实例的变量和发生 多个服务器实例会导致此中断,因为值将是 在不同的实例中有所不同,因此这种方法会导致很大 问题和应用中断...

因此,为了解决这个问题,我们需要使用 Redis 之类的东西,以便数据可以在多个节点之间共享

希望这会有所帮助!

【讨论】:

以上是关于带有粘性会话的 Nodejs 集群的主要内容,如果未能解决你的问题,请参考以下文章

生产环境中的Tomcat集群/负载均衡性能

用于 websocket 应用程序的 nginx-ingress 粘性会话

集群环境中的故障​​转移不适用于 JSF 2、Richfaces 4、Tomcat 7

微服务中的会话管理

使用粘性会话和 websocket 进行扩展

Artillery NodeJS 集群性能基准测试问题