Asynchronous Programming và Performance

Dưới đây là phần giải thích chuyên sâu về Asynchronous Programming và Performance trong Node.js theo đúng góc nhìn của một Senior Backend Developer:


1. EventEmitter trong Node.js là gì và cách sử dụng nó?

EventEmitter là gì?

EventEmitter là một lớp cốt lõi trong module events của Node.js, cho phép các đối tượng phát và lắng nghe các sự kiện tùy chỉnh — tương tự như pattern Publish/Subscribe.

Cách sử dụng:

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

// Đăng ký sự kiện
myEmitter.on('greeting', (name) => {
  console.log(`Hello, ${name}!`);
});

// Phát sự kiện
myEmitter.emit('greeting', 'Tài');

✅ Ứng dụng:

  • Xử lý kết nối socket.

  • Giao tiếp giữa các module.

  • Tích hợp vào các lớp tùy chỉnh.


2. Cách xử lý nhiều yêu cầu bất đồng bộ trong Node.js

✅ Sử dụng các kỹ thuật không đồng bộ:

  • Callback

  • Promise

  • Async/Await

  • Event Loop

✅ Sử dụng Promise.all() cho chạy song song:

const fs = require('fs').promises;

async function readFiles() {
  const [a, b] = await Promise.all([
    fs.readFile('a.txt'),
    fs.readFile('b.txt')
  ]);
  console.log(a.toString(), b.toString());
}

✅ Không block luồng chính:

  • Node.js đơn luồng (single-threaded) nhưng xử lý I/O bất đồng bộ qua Event Looplibuv threadpool.

  • Tránh các hàm fs.readFileSync() hoặc JSON.parse() trên dữ liệu lớn trong main thread.


3. Sự khác biệt giữa setTimeoutsetImmediate

setTimeout(fn, 0)

setImmediate(fn)

Thời điểm thực thi

Sau ít nhất 0ms (qua Timer phase)

Ngay sau I/O events (Check phase)

Ưu tiên

Có thể bị delay tùy lịch của Event Loop

Gần như được chạy sớm hơn

Dùng khi nào?

Khi cần trì hoãn cụ thể

Khi muốn chạy ngay sau I/O

✅ Ví dụ:

setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));

// Kết quả tùy hệ thống, nhưng thường:
// immediate
// timeout

4. Làm thế nào để tối ưu hóa hiệu suất trong ứng dụng Node.js?

✅ Một số kỹ thuật tối ưu hóa:

🔸 1. Tránh block Event Loop

  • Không xử lý logic CPU nặng trên main thread.

  • Dùng worker_threads hoặc chia task sang microservice khác.

🔸 2. Dùng cache (in-memory hoặc Redis)

  • Tránh gọi DB nhiều lần với cùng dữ liệu.

  • Dùng node-cache, lru-cache hoặc Redis.

🔸 3. Sử dụng cluster hoặc worker_threads

// cluster example
const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  for (let i = 0; i < os.cpus().length; i++) cluster.fork();
} else {
  require('./server'); // hoặc app.listen()
}

🔸 4. Sử dụng Async/Await thay vì callback

  • Code dễ đọc và bảo trì hơn.

  • Tránh "callback hell".

🔸 5. Sử dụng Streaming thay vì load toàn bộ dữ liệu

const fs = require('fs');
fs.createReadStream('large-file.txt')
  .pipe(res); // tránh memory overload

🔸 6. Giảm số lượng request/response

  • Dùng compression, HTTP2, pagination hoặc GraphQL.

🔸 7. Monitor và Benchmark

  • Sử dụng APM như PM2 + Keymetrics, New Relic, hoặc Datadog.

  • Phân tích memory leak, CPU profiling.


✅ Tổng kết nhanh:

Khái niệm

Mô tả

EventEmitter

Lớp phát và lắng nghe sự kiện tùy chỉnh

Async Handling

Dùng Promise/async-await/Promise.all để chạy song song

setTimeout vs setImmediate

Khác nhau về thời điểm ưu tiên trong Event Loop

Tối ưu hiệu suất

Cache, cluster, stream, tránh blocking, monitoring


Last updated