>  기사  >  웹 프론트엔드  >  Node.js의 성능을 최대한 활용하는 몇 가지 방법 소개programs_node.js

Node.js의 성능을 최대한 활용하는 몇 가지 방법 소개programs_node.js

WBOY
WBOY원래의
2016-05-16 15:53:201295검색

Node.JS 프로세스는 단일 물리적 코어에서만 실행되므로 확장 가능한 서버를 개발할 때 특별한 주의가 필요합니다.

안정적인 API 세트와 프로세스 관리를 위한 기본 확장 개발이 있기 때문에 병렬화할 수 있는 Node.JS 애플리케이션을 설계하는 방법은 다양합니다. 이 블로그 게시물에서는 이러한 가능한 아키텍처를 비교합니다.

이 기사에서는 프로세스를 쉽게 관리하고 2차 분산 컴퓨팅을 구현하는 데 사용할 수 있는 작은 Node.JS 라이브러리인 계산 클러스터 모듈도 소개합니다.

문제 발생

Mozilla Persona 프로젝트에서는 다양한 특성을 지닌 수많은 요청을 처리할 수 있어야 하므로 Node.JS를 사용하려고 했습니다.

사용자 경험에 영향을 주지 않기 위해 우리가 디자인한 '인터랙티브' 요청은 가벼운 컴퓨팅 소비만 필요로 하지만 UI가 멈춘 느낌이 들지 않도록 더 빠른 응답 시간을 제공합니다. 이에 비해 '일괄' 작업은 처리하는 데 약 0.5초가 걸리며, 다른 이유로 인해 더 긴 지연이 발생할 수 있습니다.


더 나은 설계를 위해 우리는 현재 요구 사항을 충족하는 많은 솔루션을 찾았습니다.
확장성과 비용을 고려하여 다음과 같은 주요 요구 사항을 나열합니다.

  • 효율성: 모든 유휴 프로세서를 효과적으로 사용할 수 있습니다
  • 응답: 우리의 "애플리케이션"은 실시간으로 빠르게 응답할 수 있습니다
  • 우아함: 처리해야 할 요청이 너무 많으면 처리할 수 있는 것만 처리합니다. 처리가 불가능할 경우 오류를 명확하게 신고해주세요
  • 단순성: 당사의 솔루션은 사용이 간편하고 편리해야 합니다.

위의 사항을 통해 명확하고 목적에 맞게 필터링할 수 있습니다

옵션 1: 메인 스레드에서 직접 처리

메인 스레드가 데이터를 직접 처리하면 결과가 매우 나쁩니다.

멀티 코어 CPU를 최대한 활용할 수는 없습니다. 대화형 요청/응답에서는 현재 요청(또는 응답)이 처리될 때까지 기다려야 하는데 이는 우아하지 않습니다.

이 솔루션의 유일한 장점은 충분히 간단하다는 것입니다

function myRequestHandler(request, response) [
 // Let's bring everything to a grinding halt for half a second.
 var results = doComputationWorkSync(request.somesuch);
}

Node.JS 프로그램에서 여러 요청을 동시에 처리하고 동기적으로 처리하려는 경우 문제가 발생할 수 있습니다.

방법 2: 비동기 처리 사용 여부

백그라운드에서 비동기 방식을 사용하면 성능이 크게 향상되나요?

백그라운드에서 실행하는 것이 적합한지 여부에 따라 반드시 답이 달라지는 것은 아닙니다

예를 들어, 다음과 같은 상황에서 메인 스레드에서 JavaScript나 로컬 코드를 사용하여 계산을 수행할 때 성능이 동기 처리보다 좋지 않은 경우 반드시 백그라운드에서 비동기 메서드를 사용하여 처리할 필요는 없습니다.

다음 코드를 읽어주세요

function doComputationWork(input, callback) {
 // Because the internal implementation of this asynchronous
 // function is itself synchronously run on the main thread,
 // you still starve the entire process.
 var output = doComputationWorkSync(input);
 process.nextTick(function() {
  callback(null, output);
 });
}
 
function myRequestHandler(request, response) [
 // Even though this *looks* better, we're still bringing everything
 // to a grinding halt.
 doComputationWork(request.somesuch, function(err, results) {
  // ... do something with results ...
 });

}
关键点就在于NodeJS异步API的使用并不依赖于多进程的应用

方案三:用线程库来实现异步处理。

只要实现得当,使用本地代码实现的库,在 NodeJS 调用的时候是可以突破限制从而实现多线程功能的。

有很多这样的例子, Nick Campbell 编写的 bcrypt library 就是其中优秀的一个。

如果你在4核机器上拿这个库来作一个测试,你将看到神奇的一幕:4倍于平时的吞吐量,并且耗尽了几乎所有的资源!但是如果你在24核机器上测试,结果将不会有太大变化:有4个核心的使用率基本达到100%,但其他的核心基本上都处于空闲状态。

问题出在这个库使用了NodeJS内部的线程池,而这个线程池并不适合用来进行此类的计算。另外,这个线程池上限写死了,最多只能运行4个线程。

除了写死了上限,这个问题更深层的原因是:

  •     使用NodeJS内部线程池进行大量运算的话,会妨碍其文件或网络操作,使程序看起来响应缓慢。
  •     很难找到合适的方法来处理等待队列:试想一下,如果你队列里面已经积压了5分钟计算量的线程,你还希望继续往里面添加线程吗?

内建线程机制的组件库在这种情况下并不能有效地利用多核的优势,这降低了程序的响应能力,并且随着负载的加大,程序表现越来越差。


方案四:使用 NodeJS 的 cluster 模块

NodeJS 0.6.x 以上的版本提供了一个cluster模块 ,允许创建“共享同一个socket”的一组进程,用来分担负载压力。

假如你采用了上面的方案,又同时使用 cluster 模块,情况会怎样呢?

这样得出的方案将同样具有同步处理或者内建线程池一样的缺点:响应缓慢,毫无优雅可言。

有时候,仅仅添加新运行实例并不能解决问题。
 

方案五:引入 compute-cluster 模块

在 Persona 中,我们的解决方案是,维护一组功能单一(但各不相同)的计算进程。

在这个过程中,我们编写了 compute-cluster 库。

这个库会自动按需启动和管理子进程,这样你就可以通过代码的方式来使用一个本地子进程的集群来处理数据。

使用例子:
 

const computecluster = require('compute-cluster');
 
// allocate a compute cluster
var cc = new computecluster({ module: './worker.js' });
 
// run work in parallel
cc.enqueue({ input: "foo" }, function (error, result) {
 console.log("foo done", result);
});
cc.enqueue({ input: "bar" }, function (error, result) {
 console.log("bar done", result);
});

fileworker.js 中响应了 message 事件,对传入的请求进行处理:
 

process.on('message', function(m) {
 var output;
 // do lots of work here, and we don't care that we're blocking the
 // main thread because this process is intended to do one thing at a time.
 var output = doComputationWorkSync(m.input);
 process.send(output);
});
 

호출 코드를 변경하지 않고도 컴퓨팅 클러스터 모듈을 기존 비동기 API와 통합할 수 있어 최소한의 코드로 진정한 멀티 코어 병렬 처리를 달성할 수 있습니다.

이 솔루션의 성능을 4가지 측면에서 살펴보겠습니다.

멀티 코어 병렬 기능: 하위 프로세스는 모든 코어를 사용합니다.

응답성: 핵심 관리 프로세스는 하위 프로세스 시작과 메시지 전달만 담당하므로 대부분의 시간 동안 유휴 상태이며 더 많은 대화형 요청을 처리할 수 있습니다.

기계의 부하가 심한 경우에도 운영 체제의 스케줄러를 사용하여 핵심 관리 프로세스의 우선순위를 높일 수 있습니다.

단순성: 비동기 API는 특정 구현 세부 정보를 숨기는 데 사용됩니다. 호출 코드를 변경하지 않고도 이 모듈을 현재 프로젝트에 쉽게 통합할 수 있습니다.

이제 부하가 갑자기 급증하더라도 시스템의 효율이 비정상적으로 떨어지지 않도록 하는 방법을 찾아보겠습니다.

물론, 압력이 급증하더라도 시스템이 여전히 효율적으로 실행되고 최대한 많은 요청을 처리할 수 있다는 것이 최선의 목표입니다.


좋은 솔루션 구현을 돕기 위해 Compute-cluster는 하위 프로세스를 관리하고 메시지를 전달하는 것 이상의 작업을 수행하며 다른 정보도 관리합니다.

현재 실행 중인 하위 프로세스 수와 각 하위 프로세스를 완료하는 데 걸리는 평균 시간을 기록합니다.

이러한 기록을 통해 하위 프로세스가 시작되기까지 걸리는 시간을 예측할 수 있습니다.

이에 따라 사용자가 설정한 매개변수(max_request_time)와 결합하여 처리 없이 시간 초과될 수 있는 요청을 직접 종료할 수 있습니다.

이 기능을 사용하면 사용자 경험을 기반으로 코드를 쉽게 작성할 수 있습니다. 예를 들어, "사용자는 로그인하는 데 10초 이상 기다리면 안 됩니다." 이는 max_request_time을 7초로 설정하는 것과 대략 동일합니다(네트워크 전송 시간을 고려해야 함).

페르소나 서비스에 대한 스트레스 테스트를 진행한 결과 매우 만족스러웠습니다.

압력이 극도로 높은 상황에서도 인증된 사용자에게 서비스를 제공할 수 있었고 인증되지 않은 일부 사용자를 차단하고 관련 오류 메시지를 표시했습니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.