在node.js中,如何声明一个可以被master进程初始化并被worker进程访问的共享变量?

Posted

技术标签:

【中文标题】在node.js中,如何声明一个可以被master进程初始化并被worker进程访问的共享变量?【英文标题】:In node.js, how to declare a shared variable that can be initialized by master process and accessed by worker processes? 【发布时间】:2012-06-13 11:28:53 【问题描述】:

我想要以下

在启动期间,主进程从文件中加载一个大表并将其保存到一个共享变量中。该表有 9 列和 1200 万行,大小为 432MB。 工作进程运行 HTTP 服务器,接受针对大表的实时查询。

这是我的代码,显然没有达到我的目标。

var my_shared_var;
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) 
  // Load a large table from file and save it into my_shared_var,
  // hoping the worker processes can access to this shared variable,
  // so that the worker processes do not need to reload the table from file.
  // The loading typically takes 15 seconds.
  my_shared_var = load('path_to_my_large_table');

  // Fork worker processes
  for (var i = 0; i < numCPUs; i++) 
    cluster.fork();
  
 else 
  // The following line of code actually outputs "undefined".
  // It seems each process has its own copy of my_shared_var.
  console.log(my_shared_var);

  // Then perform query against my_shared_var.
  // The query should be performed by worker processes,
  // otherwise the master process will become bottleneck
  var result = query(my_shared_var);

我尝试将大表保存到 MongoDB 中,以便每个进程都可以轻松访问数据。但是表太大了,即使有索引,MongoDB 也需要大约 10 秒才能完成我的查询。这太慢了,对于我的实时应用程序来说是不可接受的。我也尝试过 Redis,它将数据保存在内存中。但是 Redis 是一个键值存储,我的数据是一个表。我还写了一个 C++ 程序将数据加载到内存中,查询时间不到 1 秒,所以我想在 node.js 中进行模拟。

【问题讨论】:

memcached 是否适合此数据? 如果您的数据集增长,您可能需要重新考虑优化数据结构或数据库软件的查询。此外,对于数据库系统而言,Node.js 将是糟糕的语言选择,而您的 C++ 程序可能已经足够好了。 【参考方案1】:

如果我用几句话翻译您的问题,您需要与 WORKER 实体共享 MASTER 实体的数据。使用事件可以很容易地完成:

从大师到工人:

worker.send(json data);    // In Master part

process.on('message', yourCallbackFunc(jsonData));    // In Worker part

从工人到主人:

process.send(json data);   // In Worker part

worker.on('message', yourCallbackFunc(jsonData));    // In Master part

我希望通过这种方式您可以双向发送和接收数据。如果您觉得有用,请将其标记为答案,以便其他用户也可以找到答案。谢谢

【讨论】:

提问者询问的是“数百万行的大数据”。您的答案可能在这里不起作用。 @MupparthyRavindranath ...我的回答解释了如何在 Master 和 Worker 进程之间共享数据。如果是 db 造成了问题,那么提问者应该尽可能地对其进行规范化,或者提问者应该共享查询语句/数据库结构,以便我们可以在这个方向上提供解决方案。 我相信这些信息是相关的。如果在主进程上进行查询,它只会发回相关数据,远远少于完整的数据集。这可以通过 IPC 进行。无论如何,这些数据都需要通过 HTTP 发送,IPC 不会成为瓶颈。建议其他数据库很奇怪,因为很明显 OP 将 master 描述为数据库系统。 这不是一个“共享变量”,它是已经存储在内存中的数据的全新副本,这违背了能够从另一个工作人员访问内存中相同位置的目的。这种区别对您需要多少 RAM 有重要影响。此外,这是非常低效的,因为数据通过 JSON.parse() 和 JSON.stringify() 方法,这两个方法都会阻塞事件循环...... @Shivam 类似:github.com/jxcore/jxcore 或 github.com/SyntheticSemantics/ems【参考方案2】:

您正在寻找共享内存,node.js just does not support。您应该寻找替代方案,例如 querying a database 或使用 memcached。

【讨论】:

node.js npm 模块非常多,其中一些确实支持共享内存,例如npmjs.org/search?q=shared+memory 差不多 4 年后..@Martin Blech 我收到了question for you! 在这里投票: github.com/nodejs/help/issues/560 。这是因为没有人投票,它仍然尚未实施。 请注意,这个答案在 2012 年是正确的,但现在有诸如 Node.js WorkerThreads 之类的东西:nodejs.org/api/worker_threads.html 我已经发布了一个提到这一点的新答案。【参考方案3】:

在 node.js 中,fork 的工作方式与在 C++ 中不同。它不是复制进程的当前状态,而是运行新进程。因此,在这种情况下,变量不共享。每行代码都适用于每个进程,但主进程将 cluster.isMaster 标志设置为 true。您需要为每个工作进程加载数据。如果您的数据非常庞大,请小心,因为每个进程都有自己的副本。我认为您需要在需要时立即查询部分数据,或者在内存中确实需要它们时等待。

【讨论】:

【参考方案4】:

如果您的应用程序适合只读访问,请尝试my own shared memory module。它在幕后使用mmap,因此数据是在访问时加载的,而不是一次全部加载。内存在机器上的所有进程之间共享。使用起来超级简单:

const Shared = require('mmap-object')

const shared_object = new Shared.Open('table_file')

console.log(shared_object.property)

它为您提供了一个常规的对象接口,用于连接字符串或数字的键值存储。它在我的应用程序中非常快。

还有一个experimental read-write version of the module 可用于测试。

【讨论】:

A contributor 不久前添加了一些位以使其在 MSVS 下编译。我最近没有测试过它,也没有方便地访问 Windows 构建环境。【参考方案5】:

你可以使用 Redis。

Redis 是一个开源、BSD 许可的高级键值缓存和存储。它通常被称为数据结构服务器,因为键可以包含字符串、哈希、列表、集合、排序集合、位图和超日志。

redis.io

【讨论】:

这还行得通吗?.. 您是否还需要将数据从 Redis 传递到 Node,从而有效地破坏了共享内存的目的? 是的,它运行良好。您可以在任何需要的地方(节点块代码)从 Redis 获取数据。 Nono 我的意思是,你不需要复制吗?如果你这样做了,那么它就不再是真正的共享内存了。【参考方案6】:

这种方式可以“共享一个变量”;它比@Shivam 呈现的方式更花哨。但是,该模块在内部使用相同的 API。因此,“共享内存”有点误导,因为集群中的每个进程都是父进程的一个分支。在分叉时,进程内存在操作系统内存中复制。因此,除了像 shm 设备或虚拟共享内存页面 (Windows) 这样的低级共享内存之外,没有真正的共享内存。我确实为 Node.js 实现了一个本机模块,它确实使用了本机共享内存(这是 real 共享内存),因为使用这种技术,两个进程都直接从操作系统共享内存部分读取。但是,此解决方案在这里并不适用,因为它仅限于标量值。您当然可以 JSON.stringify 并共享 JSON 序列化数据字符串,但解析/字符串化所消耗的时间对于大多数用例来说完全不理想。 (特别是对于较大的对象,使用标准库实现对 JSON 进行解析/字符串化变得非线性)。

因此,这个解决方案似乎是目前最有希望的:

const cluster = require('cluster');
require('cluster-shared-memory');

if (cluster.isMaster) 
  for (let i = 0; i < 2; i++) 
    cluster.fork();
  
 else 
  const sharedMemoryController = require('cluster-shared-memory');
  // Note: it must be a serializable object
  const obj = 
    name: 'Tom',
    age: 10,
  ;
  // Set an object
  await sharedMemoryController.set('myObj', obj);
  // Get an object
  const myObj = await sharedMemoryController.get('myObj');
  // Mutually exclusive access
  await sharedMemoryController.mutex('myObj', async () => 
    const newObj = await sharedMemoryController.get('myObj');
    newObj.age = newObj.age + 1;
    await sharedMemoryController.set('myObj', newObj);
  );

【讨论】:

【参考方案7】:

这个问题是在 2012 年发布的,正好是 10 年前。由于没有其他答案提到它,Node.js 现在支持支持共享内存的Worker Threads。

直接来自文档:

Worker(线程)对于执行 CPU 密集型 javascript 操作很有用。 与 child_process 或 cluster 不同,worker_threads 可以共享内存。他们通过传输 ArrayBuffer 实例或共享 SharedArrayBuffer 实例来实现。

【讨论】:

以上是关于在node.js中,如何声明一个可以被master进程初始化并被worker进程访问的共享变量?的主要内容,如果未能解决你的问题,请参考以下文章

如何声明一个node.js变量,以后可以在我的js文件中使用?

WebStorm JSDoc 类声明,用于使用 node.js 模块完成代码

node.js 如何决定一个语句是不是被异步处理?

Node.js:使用 IPC 的 master 和 worker 之间的请求/响应

在 Node.js 中声明多个 module.exports

node.js的基本语法