정보실

웹학교

정보실

Nodejs 노드 워커의 사용 사례

본문

과거에는 CPU 집약적인 계산이 필요한 응용 프로그램을 구축 할 때 Node.js가 종종 옵션이 아니었습니다. 이것은 비 차단 이벤트 중심 I / O 아키텍처 때문입니다. Node.js에 스레드 워커가 등장하면서 이를 CPU 집약적인 애플리케이션에 사용할 수 있습니다. 

이 기사에서는 Node.js 애플리케이션에서 작업자 스레드의 특정 사용 사례를 살펴 보겠습니다.


https://dev.to/bnevilleoneill/use-cases-for-node-workers-o1n 


Node.js에서 스레드 워커의 사용 사례를 계속하기 전에 노드의 I / O 바운드와 CPU 바운드를 빠르게 비교해 봅시다.


Node.js의 I / O 바인딩과 CPU 바인딩 


I/O bound 

자원의 증가로 인해 프로그램의 성능이 향상되는 경우 프로그램은 자원에 의해 구속된다고 합니다. 메모리, 하드 디스크 속도 또는 네트워크 연결과 같은 I / O 하위 시스템의 속도가 증가하면 I / O 바운드 프로그램의 성능이 향상됩니다. 이벤트 루프는 종종 코드 실행을 계속하거나 응답을 반환하기 전에 네트워크, 파일 시스템 및 데이터베이스 I / O가 작업을 완료하기를 기다리는 데 시간이 걸리기 때문에 Node.js 응용 프로그램에서 일반적입니다. 하드 디스크 속도 및 / 또는 네트워크 연결을 늘리면 일반적으로 응용 프로그램 또는 프로그램의 전체 성능이 향상됩니다.


CPU bound 


처리 시간이 CPU 증가에 의해 감소하면 프로그램은 CPU 바운드입니다. 예를 들어, 파일의 해시를 계산하는 프로그램은 2.2GHz 프로세서에서 더 빠르게 처리되고 1.2GHz에서 느리게 처리됩니다.


CPU 바운드 응용 프로그램의 경우 대부분의 시간이 CPU를 사용하여 계산에 소비됩니다. Node.js에서 CPU 바운드 애플리케이션은 이벤트를 차단하고 다른 요청을 보류 시킵니다.


Node.js 황금률 


이벤트 루프를 차단하지 말고 실행 상태를 유지하고 스레드와 같은 동기식 네트워크 호출 또는 무한 루프를 차단할 수 있는 것은 피하십시오. 


노드는 비 차단 I / O 호출을 사용하여 단일 스레드 이벤트 루프에서 실행되므로 동시에 여러 개의 수신 HTTP 요청을 처리하는 등 수만 건의 계산을 동시에 지원할 수 있습니다. 주어진 시간에 각 클라이언트와 관련된 작업이 작은 경우에는 잘 작동하며 빠릅니다. 그러나 CPU 집약적 인 계산을 수행하면 동시 Node.js 서버가 급격히 중단됩니다. 다른 수신 요청은 한 번에 하나의 요청 만 제공되므로 대기합니다.


Node.js의 CPU 집약적 작업에 대처하기 위해 특정 전략이 사용되었습니다. CPU가 최적으로 사용되도록 하는 다중 프로세스 (클러스터 API와 같은), 하위 프로세스는 블로킹 작업을 처리하기 위해 새로운 프로세스를 생성합니다.


이러한 전략은 이벤트 루프가 차단되지 않고 프로세스를 분리 할 수 ​​있으므로 한 프로세스에서 문제가 발생하더라도 다른 프로세스에 영향을 미치지 않기 때문에 유리합니다. 그러나 자식 프로세스는 개별적으로 실행되므로 서로 메모리를 공유 할 수 없으며 데이터 통신은 JSON을 통해 이루어 져야합니다. 이 경우 데이터의 직렬화 및 역 직렬화가 필요합니다.


Node.js에서 CPU를 많이 사용하는 계산을 위한 최상의 솔루션은 동일한 프로세스 내에서 여러 개의 Node.js 인스턴스를 실행하는 것입니다. 여기서 메모리를 공유 할 수 있으며 JSON을 통해 데이터를 전달할 필요가 없습니다. 이것이 바로 작업자 스레드가 Node.js에서 하는 일입니다.


worker threads display 


스레드 워커로 수행 할 수있는 실제 CPU 집약적 작업 


Node.js 애플리케이션에서 쓰레드 워커의 몇 가지 사용 사례를 살펴 보겠습니다. 우리는 노드 워커에서 쓰레드 워커의 사용 사례 만 살펴볼 것이기 때문에 쓰레드 워커 API를 보지 않을 것입니다. 스레드 작업자에 익숙하지 않은 경우 이 게시물을 방문하여 스레드 작업자 API 사용 방법을 시작할 수 있습니다.


이미지 크기 조정 


사용자가 프로필 이미지를 업로드하고 응용 프로그램 내의 다양한 사용 사례에 대해 여러 크기 (예 : 100 x 100 및 64 x 64)의 이미지를 생성 할 수 있는 응용 프로그램을 구축한다고 가정 해 보겠습니다. 이미지 크기를 조정하는 프로세스는 CPU를 많이 사용하며 두 가지 다른 크기로 크기를 조정해야 CPU의 이미지 크기 조정에 소요되는 시간도 늘어납니다. 이미지 크기를 조정하는 작업은 별도의 스레드로 아웃 소싱 할 수 있지만 주 스레드는 다른 경량 작업을 처리합니다.

// worker.js
const { parentPort, workerData } =  require("worker_threads");
const  sharp  =  require("sharp");

async  function  resize() {

    const  outputPath  =  "public/images/" + Date.now() +  ".png";
    const { image, size } =  workerData;

    await  sharp(image)
    .resize(size, size, { fit:  "cover" })
    .toFile(outputPath);
 parentPort.postMessage(outputPath);
}
resize()


// mainThread.js
const { Worker } =  require("worker_threads");

module.exports  =  function  imageResizer(image, size) {

    return  new  Promise((resolve, reject) => {
    const  worker  =  new  Worker(__dirname  +    "/worker.js", {
workerData: { image, size }
});
    worker.on("message", resolve);
    worker.on("error", reject);
    worker.on("exit", code  => {
        if (code  !==  0)
            reject(new  Error(`Worker stopped with exit code ${code}`));
        });
    });
};

메인 스레드에는 각 이미지의 크기를 조정하기 위한 스레드를 만드는 방법이 있습니다. workerData 속성을 사용하여 크기와 이미지를 스레드 워커에 전달합니다. 작업자는 이미지 크기를 선명하게 조정하고 다시 메인 스레드로 보냅니다.


Video compression 


비디오 압축은 스레드 작업자에게 아웃 소싱 할 수 있는 또 다른 CPU 집약적 작업입니다. 대부분의 비디오 스트리밍 응용 프로그램에는 일반적으로 네트워크 연결에 따라 사용자에게 표시되는 단일 비디오의 여러 변형이 있습니다. 스레드 작업자는 비디오를 다양한 크기로 압축하는 작업을 수행 할 수 있습니다.


ffmpeg-fluet는 Node.js 응용 프로그램에서 비디오 처리에 일반적으로 사용되는 모듈입니다. ffmpeg는 오디오 및 비디오를 기록, 변환 및 스트리밍 하기 위한 완벽한 크로스 플랫폼 솔루션인 ffmpeg에 의존합니다.


새 스레드를 사용해야 할 때마다 작업자를 생성해야 하는 오버 헤드 때문에 작업자를 즉시 ​​생성하는 대신 작업자가 필요할 때 사용할 수 있는 작업자 풀을 만드는 것이 좋습니다. 작업자 풀을 만들려면 NPM 모듈 node-worker-threads-pool을 사용하고 Node의 worker_threads 모듈을 사용하여 작업자 스레드 풀을 만듭니다.


// worker.js
const { parentPort, workerData } =  require("worker_threads");
const  ffmpeg  =  require("fluent-ffmpeg");

function  resizeVideo({ inputPath, size, parentPort }) {
    const  outputPath  =  "public/videos/"  +  Date.now() +  size  +  ".mp4";
    ffmpeg(inputPath)
    .audioCodec("libmp3lame")
    .videoCodec("libx264")
    .size(size)
    .on("error", function(err) {
    console.log("An error occurred: "  +  err.message);
    })
    .on("end", function() {
    parentPort.postMessage(outputPath);
    })
    .save(outputPath);
}

parentPort.on("message", param  => {
    resizeVideo({ ...param, parentPort });
});


// mainThread.js
const { StaticPool } =  require("node-worker-threads-pool");

  const  filePath  =  __dirname  +  "/worker.js";
  const  pool  =  new  StaticPool({
        size:  4,
        task:  filePath,
        workerData:  "workerData!"
    });

  const  videoSizes  = ["1920x1080", "1280x720",   "854x480", "640x360"];

module.exports  =  async  function compressVideo(inputPath) {
    const  compressedVideos  = [];
    videoSizes.forEach(async  size  => {
        const  video  =  await  pool.exec({ inputPath, size });
        compressedVideos.push(video);
    });
};


파일 무결성 


클라우드 스토리지에 파일을 저장해야 한다고 가정하십시오. 저장 한 파일이 제 3 자에 의해 변조 되지 않았는지 확인하려고 합니다. 암호화 해시 알고리즘을 사용하여 해당 파일의 해시를 계산하여 이를 수행 할 수 있습니다. 이러한 해시와 해당 저장 위치를 ​​데이터베이스에 저장합니다. 파일을 다운로드 할 때 해시를 다시 계산하여 일치하는지 확인합니다. 해시 계산 프로세스는 CPU를 많이 사용하며 스레드 워커에서 수행 할 수 있습니다.


// hashing.js
const {
  Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
const  crypto  =  require("crypto");
const  fs  =  require("fs");


if (isMainThread) {
  module.exports = async function hashFile(filePath) {
    return new Promise((resolve, reject) => {
      const worker = new Worker(__filename);
      worker.on('message', resolve);
      worker.on('error', reject);
      worker.on('exit', (code) => {
        if (code !== 0)
          reject(new Error(`Worker stopped with exit code ${code}`));
      });
      worker.postMessage(filePath)
    });
  };
} else {
    const  algorithm  =  "sha1";
    const  shasum  =  crypto.createHash(algorithm);
    const  stream  =  fs.ReadStream(filePath);
    stream.on("data", function(data) {
        shasum.update(data);
    });
    stream.on("end", function() {
        const  hash  =  shasum.digest("hex");
        parentPort.postMessage(hash);
    });
}


작업자 스레드 코드와 기본 스레드 코드가 동일한 파일에 있습니다. 스레드 워커의 isMainThread 속성은 현재 스레드를 결정하고 각 스레드에 적합한 코드를 실행하는 데 도움이 됩니다. 기본 스레드는 새 작업자를 만들고 작업자의 이벤트를 수신합니다. 작업자 스레드는 createHash라는 Node.js 암호화 방법을 사용하여 데이터 스트림의 해시를 계산합니다.


결론 


Node.js 스레드 워커는 이벤트 루프를 해제하여 성능을 향상 시키려는 훌륭한 옵션입니다. 주목해야 할 것은 작업자가 CPU 집약적 인 JavaScript 작업을 수행하는 데 유용하다는 것입니다. 비동기 적으로 작업을 수행하기 위한 Node.js의 내장 메커니즘은 이미 작업자 스레드 보다 더 효율적으로 처리하므로 I / O에 사용하지 마십시오.



  • 트위터로 보내기
  • 페이스북으로 보내기
  • 구글플러스로 보내기
  • 카카오톡으로 보내기

페이지 정보

조회 7회 ]  작성일19-10-09 10:58

웹학교