Hiểu về cluster trong Node.js – đa luồng trong Node.js

0

Từ trước tới nay, các bạn được học về Node.js đều được bảo là Node.js chỉ xử lý đơn luồng. Tức là tại một thời điểm, chỉ có một Thread được thực hiện.

Nói đơn giản cho dễ hiểu: bạn có CPU 8 nhân, 16 threads. Giờ bạn muốn duyệt một 1 triệu records để tìm phần tử lớn nhất. Với node.js, sẽ chỉ có 1 thread của CPU là thực hiện công việc duyệt tìm vì mặc định Node.js là single-thread. 1 thread chạy cắm đầu, 7 threads kia ngồi cười khúc khích.

Nhưng với Java, công việc được chia đều ra cho các threads, nên tốc độ sẽ xử lý trong bài toán ví dụ này sẽ nhanh hơn.

Đến đây, mình tin là bạn sẽ bật ra thắc mắc: Vậy không có cách nào để Node.js thực hiện đa luồng à?

Thế mạnh của Node.js là cơ chế none-blocking I/O, giúp ứng dụng có tốc độ rất nhanh. Tuy nhiên, có những bài toán yêu cầu phải xử lý đa luồng để tận dụng sức mạng CPU đa nhân. Tất nhiên là có thể làm được nhờ sự trợ giúp của cluster.

Bài viết này, chúng ta sẽ cùng nhau tìm hiểu về Cluster trong Node.js

Cluster trong Node.js
Cluster trong Node.js

Khi nào nên sử dụng Cluster

Như mình đã đề cập ở trên, thế mạnh của Node.js là none-blocking I/O, điều này giúp cho ứng dụng mặc dù chỉ sử dụng 1 thread để chạy nhưng tốc độ thì không thể tồi chút nào. Đơn giản vì Node.js tận dụng được thời gian nhàn rỗi của CPU.

Tuy nhiên, điều này chỉ phù hợp với các ứng dụng mà mỗi tác vụ chỉ thực hiện trong thời gian ngắn. Kiểu như các ứng dụng chat, ứng dụng Real-Time…

Còn với các ứng dụng nặng về tính toán như xử lý ảnh, crawler data… thì đơn luồng như Node.js không phù hợp.

Nếu bạn vẫn “chày cối” chọn Node.js thì vẫn có cách. Đó là sử dụng cluster để Node.js có thể xử lý đa luồng, tận dụng các nhân CPU còn nhàn rỗi.

Cluster trong Node.js hoạt động như thế nào?

Cluster trong Node.js được tạo một cách đơn giản bằng cách sử dụng một module cùng tên: Cluster module.

Cơ chế hoạt động của nó cũng khá đơn giản. Cluster module cho phép Node.js tạo số luồng nhỏ hơn hoặc bằng số lõi của CPU, tự động phân chia công việc để tận dụng sức mạnh của CPU.

Cách sử dụng Cluster

Lý thuyết thì cơ bản vậy thôi, chúng ta sẽ cùng nhau thực hành tạo cluster để xử lý đa luồng trong Node.js

Đoạn code dưới mình sẽ tạo một server lắng nghe cùng một port thông qua cluster.

/*
  Đoạn code demo tạo clusters
*/
'use strict';

// Importing các Modules cần thiết
const http = require('http'),
    cluster = require('cluster'),
    os = require('os').cpus().length;

/*
Chúng ta sẽ tạo số cluster tương ứng với số nhân của CPU.
*/
if (cluster.isMaster) {

    for (let i = 0; i < os; i++) {
        cluster.fork();
        console.log(`The Worker number: ${i + 1} is alive`);
    }

    cluster.on('exit', (worker) => {
        console.log(`The Worker number: ${worker.id} has died`);
    });

} else {

    // Chúng ta sẽ tạo các cluster cùng lắng cùng một port
    http.createServer((sol, res) => {
        res.end('Hi, we are harnessing the power of clusters :)');
    }).listen(3000, () => console.log('The server is running on the port:3000'));

    console.log(`The Worker number: ${cluster.worker.id} is running`);
}

Như đoạn code trên, cluster đầu tiên được tìm thấy sẽ là master cluster. Sau đó thì sẽ nhân bản ra các cluster từ master cluster, đó chính là cách mà chúng ta chia sẻ port. Một listener cũng được thêm vào cluster để biết được trạng thái stop/failed của một worker.

Sử dụng Cluster kết hợp với Express.js

Ở ví dụ trên, chúng ta đã biết tạo cluster với http module. Giờ chúng ta sẽ làm một ví dụ về cách sử dụng cluster khi kết hợp http và express.js

/*
  Creating clusters and serving an application that uses express

*/
'use strict';

// importing the modules
const cluster = require('cluster'),
      os      = require('os').cpus().length,
      server  = require('./serverHttp');

/*
 We see if it is the master cluster in case it is,
 we will clone the cluster amount at the same time as the cores
 of the processor.
*/
if(cluster.isMaster){

  const Master = require('./work');
  const master = new Master({cluster:cluster});

  for(let i = 0; i < os; i++){
    master.liftWorker();
  }

  cluster.on('exit', (worker)=>{
    console.log(worker)
    console.log(`The Worker number: ${worker.id} is dead.`);
    master.liftWorkerError();
  });

}else{

  // Creating a server with http and express.
  let app = new server();
  app.initiate();
  console.log(`cluster ${cluster.worker.id} is running.`)

}

Bạn thấy đấy, cách viết code cũng tương tự như lúc trước. Chỉ khác là lần này chúng ta sẽ chia công việc thành nhiều module.

>> Đọc thêm về cách chia module: Node.js Module là gì? Cách tạo và sử dụng module trong nodejs

// Class to pick cluster workers
class Master {
    constructor(config){
  
      this.config = config || {};
      this.cluster = this.config.cluster;
  
    }
  
    // Pick up a new Worker
    liftWorker(){
  
      let worker = this.cluster.fork();
      console.log(`The worker ${worker.id} is started.`);
  
    }
  
    // Raise a worker when one dies in case of errors
    liftWorkerError(){
      let worker = this;
  
      setTimeout(()=>{
        worker.liftWorker();
      }, 200);
  
    }
  }
  
  module.exports = Master;

Về cơ bản là chúng ta sẽ tạo riêng một file là work.js. Mục đích của module này là cung cấp các phương thức để tạo mới một hoặc nhiều workers khi một worker bị die.

Cuối cùng là chúng ta sẽ có module chính để tạo server và lắng nghe một port.

'use strict';

const http = require('http'),
  express  = require('./express');

// Creating the server with http
class Servidor{
  constructor(config){
    this.config = config || {};

    this.app = new express();

    this.server = http.createServer(this.app.server);

    this.app.gets();

  }

  // Starting the server
  initiate(){
    
    this.server.listen(3000, ()=> console.log('The server is running on port 3000'));

  }
}

module.exports = Servidor;
'use strict';

const express = require('express');

class Server {
  constructor(config){
    this.config = config || {};
    this.server = express();
  }

  // Serving the routes get
  gets(){

    this.server.get('/', (sol, res, next)=>{
      
      res.send(`This route is served by the workes`);

    });

    this.server.get(`/hello`,(sol, res, next)=>{

      res.send('This route too 🙈');

    });

  }

}

module.exports = Server;

Kết luận

Node.js là giải pháp thích hợp để xử lý các ứng dụng có lượng traffic lớn. Tuy nhiên, với các ứng dụng nặng về tính toán thì Node.js cũng có thể làm tốt.

Mình biết về cluster trong Node.js cũng khá lâu rồi. Nhưng hồi đó cluster còn chưa ổn định, và ngon lành như bây giờ. Giờ thì ngon lành rồi.

Tất nhiên, cũng như các ngôn ngữ, giải pháp khác, việc xử lý đa luồng chưa bao giờ là dễ cả, code sẽ cần nhiều logic hơn. Nhưng nhìn chung, cluster sẽ giúp bạn đơn giản hóa rất nhiều việc xử lý đa luồng trong Node.js

Cảm nhận của bạn về cluster khi sử dụng trong dự án như thế nào? Để lại bình luận bên dưới nhé.

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng

Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Thông báo