【问题标题】:How does NodeJS handle multi-core concurrency?NodeJS 如何处理多核并发?
【发布时间】:2015-07-11 07:14:06
【问题描述】:

目前我正在开发一个由另一个 java 应用程序更新的数据库,但需要一个 NodeJS 应用程序来提供 Restful API 以供网站使用。为了最大限度地提高 NodeJS 应用程序的性能,它被集群化并在多核处理器中运行。

但是,据我了解,集群化的 NodeJS 应用程序在每个 CPU 核心上都有自己的事件循环,如果是这样,这是否意味着,对于集群架构师,NodeJS 将不得不像其他多线程架构师一样面对传统的并发问题,例如,写入没有写保护的同一个对象?或者更糟糕的是,因为它是同时运行的多进程,而不是一个进程中的线程被另一个阻塞......

我一直在互联网上搜索,但似乎没有人关心这一点。谁能解释一下 NodeJS 的集群架构师?非常感谢

添加:
澄清一下,我用的是 express,它不像在不同的端口上运行多个实例,它实际上是在同一个端口上监听,而是在每个 CPU 上都有一个进程竞争处理请求......

我现在想知道的典型问题是:基于给定对象 B 更新对象 A 的请求(未完成),另一个使用给定对象 C 再次更新对象 A 的请求(在第一次请求之前完成)......然后结果将基于对象 B 而不是 C,因为第一个请求实际上在第二个请求之后完成。
这在真正的单线程应用程序中不会有问题,因为第二个总是在第一个请求之后执行......

【问题讨论】:

  • AFAIK,它没有。节点是单线程的。您可以在单独的进程中运行应用程序的多个实例,但为了使其工作,应用程序必须是无状态的。这通常意味着将所有可能影响除当前请求之外的任何内容的数据存储在外部数据存储中。 Redis 为这种设置提供了几个有用的数据结构,用于传统数据库会过度杀伤的对象。

标签: node.js concurrency


【解决方案1】:

你问题的核心是:

NodeJS 将不得不像在其他多线程架构中一样面临传统的并发问题,例如,写入没有写保护的同一个对象?

答案是这种情况通常是不可能的,因为 node.js 进程不共享内存。进程 A 中的 ObjectA、ObjectB 和 ObjectC 与进程 B 中的 ObjectA、ObjectB 和 ObjectC 不同。并且由于每个进程都是单线程的,所以不会发生争用。这是您发现 node.js 没有附带信号量或互斥模块的主要原因。此外,node.js 没有附带线程模块

这也解释了为什么“没人在乎”。因为他们认为这不可能发生。

node.js 集群的问题是缓存之一。因为进程 A 中的 ObjectA 和进程 B 中的 ObjectA 是完全不同的对象,所以它们会有完全不同的数据。对此的传统解决方案当然不是在应用程序中存储动态状态,而是将它们存储在数据库中(或 memcache)。如果需要,也可以在代码中实现自己的缓存/数据同步方案。毕竟数据库集群就是这样工作的。

当然,node 作为一个用 C 编写的程序,可以很容易地用 C 扩展,并且 npm 上有一些模块可以实现线程、互斥锁和共享内存。如果您故意选择违背 node.js/javascript 的设计理念,那么您有责任确保不会出错。


补充答案:

基于给定对象 B 更新对象 A 的请求(未完成),另一个使用给定对象 C 再次更新对象 A 的请求(在第一次请求之前完成)...然后结果将基于对象 B 而不是 C ,因为第一个请求实际上在第二个请求之后完成。 这在真正的单线程应用程序中不会有问题,因为第二个总是在第一个请求之后执行......

首先,让我澄清你的一个误解。这对于real single-threaded application 来说不是问题。这是一个伪代码中的单线程应用程序:

function main () {
    timeout = FOREVER
    readFd = []
    writeFd = []

    databaseSock1 = socket(DATABASE_IP,DATABASE_PORT)
    send(databaseSock1,UPDATE_OBJECT_B)

    databaseSock2 = socket(DATABASE_IP,DATABASE_PORT)
    send(databaseSock2,UPDATE_OPJECT_C)

    push(readFd,databaseSock1)
    push(readFd,databaseSock2)

    while(1) {
        event = select(readFD,writeFD,timeout)
        if (event) {
            for (i=0; i<length(readFD); i++) {
                if (readable(readFD[i]) {
                    data = read(readFD[i])

                    if (data == OBJECT_B_UPDATED) {
                        update(objectA,objectB)
                    }
                    if (data == OBJECT_C_UPDATED) {
                        update(objectA,objectC)
                    }
                }
            }
        }
    }
}

如您所见,上面的程序中没有线程,只是使用select 系统调用的异步I/O。上面的程序可以很容易地直接翻译成单线程的 C 或 Java 等(实际上,类似的东西是 javascript 事件循环的核心)。

但是,如果对 UPDATE_OBJECT_C 的响应在对 UPDATE_OBJECT_B 的响应之前到达,则最终状态将是 objectA 根据 objectB 的值而不是 objectC 的值进行更新。

任何语言的异步单线程程序都无法避免这种情况,node.js 也不例外。

但是请注意,您最终不会处于损坏状态(尽管您最终会处于意外状态)。多线程程序的情况更糟,因为没有锁/信号量/互斥锁,对update(objectA,objectB) 的调用可能会被对update(objectA,objectC) 的调用中断,并且objectA 将被破坏。这是您在单线程应用程序中不必担心的问题,在 node.js 中您也不必担心。

如果您需要严格的时间顺序更新,您仍然需要等待第一次更新完成,将第一次更新标记为无效或为第二次更新生成错误。通常对于网络应用程序(如 stackoverflow)会返回错误(例如,如果您尝试提交评论而其他人已经更新了 cmets)。

【讨论】:

  • 非常感谢,帮了大忙,但是,出于好奇,有没有办法防止达到意外状态?这是数据库内部的工作吗?
猜你喜欢
  • 2018-12-11
  • 2018-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-21
  • 1970-01-01
  • 2018-02-06
相关资源
最近更新 更多