在node.js中,如何声明一个共享变量,可以由主进程初始化并由工作进程访问?

29

我想要以下内容:

  • 在启动期间,主进程从文件中加载一个大表,并将其保存到共享变量中。该表具有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 Тў»тљджђѓтљѕтГўтѓеУ┐ЎС║ЏТЋ░ТЇ«№╝Ъ - sarnold
如果你的集合增长了,你可能需要重新考虑优化数据结构或数据库软件的查询。此外,Node.js 对于数据库系统来说是一个糟糕的语言选择,而你的 C++ 程序可能已经足够好了。 - Shane Hsu
7个回答

16
如果用简短的话概括你的问题,那么就是你需要分享主实体的数据给工作实体。这可以通过事件轻松地完成: 从主实体到工作实体:
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

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


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

10

3
有很多 Node.js 的 npm 模块,其中一些支持共享内存,例如:https://www.npmjs.org/search?q=shared+memory - simonhf
将近4年过去了.. @Martin Blech,我有一个问题要问你! - NiCk Newman
请在此处投票:https://github.com/nodejs/help/issues/560 。由于没有人投票,所以它仍然未被实现。 - Pacerier
请注意,此答案在2012年是正确的,但现在有Node.js WorkerThreads这样的东西:https://nodejs.org/api/worker_threads.html我已发布了一个新答案,提到了这一点。 - Mauvis Ledford

6
如果您的应用程序仅需要只读访问权限,请尝试使用我的共享内存模块。 它在底层使用mmap,因此数据在访问时逐步加载,而不是一次性全部加载。 内存在整个机器上的所有进程之间共享。 使用它非常简单:
const Shared = require('mmap-object')

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

console.log(shared_object.property)

它为字符串或数字的键值存储提供了常规对象接口。在我的应用程序中非常快。
还有一个实验性的读写版本可供测试。可以在此处找到。

一位贡献者(https://github.com/druide)在一段时间前添加了一些内容,以便在MSVS下进行编译。我最近没有测试过它,并且没有方便的访问Windows构建环境。 - Allen Luce

6
在Node.js中,fork不像在C ++中那样工作。它不会复制当前进程状态,而是运行新进程。因此,在这种情况下,变量不共享。每行代码都为每个进程工作,但主进程的cluster.isMaster标志设置为true。您需要为每个工作进程加载数据。如果您的数据真的很大,请小心,因为每个进程都会有自己的副本。我认为您需要在需要时立即查询数据的部分,或者等待(如果您确实需要将其全部保存在内存中)。

2

你可以使用Redis。

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

redis.io


1
这个能行吗?..你不还需要将数据从Redis传递到Node,这实际上是在打破共享内存的目的吗? - Pacerier
是的,它完美地运作着。你可以在任何需要的地方(节点块代码)从Redis获取数据。 - Reza Roshan
2
不,我的意思是,你不需要制作一个副本吗?如果你这样做了,那么它就不再是真正的共享内存了。 - Pacerier

0
这个问题是在2012年发布的,正好是10年前。由于没有其他答案提到它,现在Node.js支持Worker Threads,支持共享内存。
直接从文档中摘录:
工作线程(线程)非常适用于执行CPU密集型JavaScript操作。与child_process或cluster不同,worker_threads可以共享内存。它们通过传输ArrayBuffer实例或共享SharedArrayBuffer实例来实现共享内存。

0

这种方法可以实现“共享变量”,比@Shivam演示的方法更为高级。然而,该模块内部使用的是相同的API。因此,“共享内存”有点误导,因为在集群中,每个进程都是父进程的分支。在fork时,进程内存在操作系统内存中被复制。因此,除了低级别的共享内存(如shm设备或虚拟共享内存页面(Windows))外,不存在真正的共享内存。我为Node.js实现了一个本地模块,它使用本地共享内存(这是真正的共享内存),因为使用这种技术,两个进程直接从操作系统共享内存部分读取。但是,这个解决方案并不适用于标量值之外的情况。当然,您可以对JSON序列化的数据字符串进行JSON.stringify和共享,但解析/字符串化所消耗的时间对于大多数用例来说都非常不理想(特别是对于较大的对象,使用标准库实现的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);
  });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接