Giới hạn tài nguyên dịch vụ sử dụng pm2

Giới hạn tài nguyên dịch vụ sử dụng pm2

Tin ngắn hàng ngày dành cho bạn
  • Rust sinh ra để tái định nghĩa nhiều thứ. Trong đó chắc phải kể đến JavaScript. Từ đầu năm đến giờ các công cụ làm từ Rust mà để cho JavaScript dùng đếm sương sương cũng vài ba cái rồi. Mới đây nhất là Oxc.

    Oxc là một công cụ phân tích cú pháp (parser), kiểm tra lỗi (lint), định dạng (formatter), chuyển đổi (transformer), minifier... tất cả đều được viết bằng Rust, trong một công cụ duy nhất.

    Mặc dù vẫn đang trong quá trình xây dựng nhưng thử nhìn điểm hiệu năng của nó so với swc hoặc eslint mà xem 🫣

    » Xem thêm
  • Mấy hôm nay, à mà cũng không hẳn, do sự kiện WWDC vừa rồi nên Apple lại bị dân cư mạng mang ra bàn tán rằng rốt cục thì các tính năng AI của họ đang ở đâu? Trong khi các hãng khác đang lao mình vào việc mang AI lên thiết bị, phần mềm của họ thì Apple lại đang có vẻ... không quan tâm lắm.

    Thậm chí mới đây các nhà nghiên cứu của Apple cho rằng các mô hình LLM sẽ "sụp đổ hoàn toàn về độ chính xác" khi được đưa ra các vấn đề cực kỳ phức tạp. Chỉ ra rằng suy luận chỉ là huyễn hoặc thì ngay lập tức đã có nhiều bài phản bác nghiên cứu này. Một lần nữa cho thấy rằng Apple đang suy nghĩ điều gì với AI trên thiết bị của họ?

    Mình thì nghĩ đơn giản thôi, Apple có vẻ đang gặp khó khăn với việc tạo ra AI cho riêng họ. Tức là khó khăn ngay từ đoạn thu thập dữ liệu để đào tạo rồi. Họ luôn tỏ ra tôn trọng quyền riêng tư của người dùng nên chẳng lẽ lại lên mạng đi xào nấu dữ liệu ở khắp nơi, hoặc "chôm" dữ liệu dưới máy người dùng lên? Chắc chắn, họ cũng không muốn cung cấp thêm dữ liệu người dùng cho các bên thứ 3 như OpenAI.

    Nhưng nhờ những khó khăn này biết đâu họ lại tìm ra được hướng đi mới. Ai cũng chọn phần dễ thì gian khổ để phần cho ai 😁. À mình không phải là "fan" của Apple, chỉ là thấy cái nào phù hợp thì dùng thôi 🤓

    » Xem thêm
  • Người "nhạy cảm" với markdown đó là khi thấy một thư viện tạo khung soạn thảo mới là nhảy ngay vào xem nó có gì mới. Milkdown/milkdown là một ví dụ.

    Xem thử thì thấy ổn phết mọi người ạ. Vài nữa thử tích hợp vào opennotas xem sao. Mang tiếng là ứng dụng ghi chú hỗ trợ markdown cơ mà cái thư viện tiptap nó không chịu làm thêm phần hỗ trợ markdown 😩. Dùng thư viện ngoài thì vẫn chưa ngon cho lắm.

    » Xem thêm

Vấn đề

Xin chào độc giả của 2coffee.dev, rất lâu rồi mới gặp lại mọi người. Cách đây một hai tuần trước, tôi gặp phải một vấn đề khá thú vị khi triển khai hệ thống. Định thôi không viết nhưng nghĩ lại chắc sẽ có người gặp phải trường hợp này nên lại cặm cụi viết ra. Âu cũng là một lần ghi chép để nhớ và chia sẻ nó đến với mọi người.

Hệ thống mà tôi đảm nhiệm có một dịch vụ (service) khá cũ, được triển khai dựa trên pm2 bằng hạ tầng VM của GCP. Gọi là cũ vì nó đã chạy rất lâu rồi, từ trước cả khi nhận việc và chẳng có thêm bản cập nhật nào nữa. Tất cả chức năng đều trong giai đoạn ổn định, chỉ dừng lại ở mức duy trì cho một tệp người dùng nhất định. Chuyện chẳng có gì nếu như gần đây số lượng người sử dụng bỗng tăng lên, hoặc cũng có khi là do một nguyên nhân nào đó mà lượng người dùng có logic phức tạp khiến hệ thống thi thoảng lại rơi vào trường hợp quá tải. CPU tăng, RAM tăng đến một mức nào đó... Bùm! Máy chủ lăn ra chết.

VM này chỉ được phân bổ lượng tài nguyên khiêm tốn: 1CPU 2GB RAM. Nên khi CPU hoặc RAM tăng lên đột ngột thì nó sẽ bị treo mà không thể ssh vào được. Nhận thấy sự cố, ngay lập tức tôi bắt tay vào đi tìm giải pháp. Trước mắt có thể nâng tài nguyên máy chủ lên nhưng thực tế đã chứng minh điều đó không hiệu quả, máy chủ vẫn "quay đơ" ra ở một thời điểm nào đó không thể đoán trước. Cũng không thể sửa lỗi ngay được vì nguồn lực có hạn mà chúng tôi vẫn còn nhiều công việc khác cần ưu tiên hơn. Lúc này điều khả thi nhất nghĩ ra là giới hạn lượng tài nguyên sử dụng xuống cho dịch vụ này.

Giới hạn bộ nhớ

Thật may mắn vì pm2 có chức năng giới hạn bộ nhớ sử dụng. Khi thiết lập giới hạn này, mỗi khi tiến trình sử dụng bộ nhớ tới hạn thì nó tự khởi động lại nhằm mục đích giải phóng bộ nhớ. Việc tràn bộ nhớ rất nguy hiểm trong VM vì nó khiến máy chủ bị treo mà không thể thao tác được gì, kể cả việc ssh vào máy chủ để khắc phục sự cố rất khó khăn.

Thiết lập rất đơn giản. Chỉ cần chạy một lệnh.

pm2 start api.js --max-memory-restart 300M

Với --max-memory-restart là giới hạn bộ nhớ. Mỗi 30 giây, pm2 sẽ quét một lần và khởi động lại dịch vụ nếu cần.

Tưởng chỉ cần giới hạn bộ nhớ là xong nhưng tiếp tục theo dõi thì một vấn đề khác lại xuất hiện: CPU cũng tăng theo.

Giới hạn CPU

pm2 không có tính năng giới hạn tài nguyên CPU cho một dịch vụ. Nếu muốn giới hạn cần phải tìm một công cụ khác hoặc công cụ hỗ trợ. Ví dụ nếu dùng Docker thì đã có sẵn cấu hình tài nguyên được phép sử dụng. Rất tiện. Sau một hồi tìm kiếm, tôi tìm thấy cpulimit là một công cụ độc lập giúp giới hạn tài nguyên CPU cho một tiến trình.

Mỗi một dịch vụ trong pm2 chạy trong một tiến trình. Khi gõ pm2 ls sẽ thấy một cột có tiêu đề PID - tương ứng với Process ID của dịch vụ đó. Khi dùng lệnh ps -fp PID sẽ thấy thông tin chi tiết của tiến trình.

Sử dụng cpulimit tương đối đơn giản. Sau khi cài đặt, sử dụng lệnh.

$ cpulimit -p PID -l 80 -b

Với PID là PID của tiến trình cần giới hạn, -l là mức CPU tối đa và -b để chạy tiến trình ở dưới nền (background). cpulimit giữ mức sử dụng CPU không vượt quá mức được thiết lập, vì thế vào giờ cao điểm máy chủ có thể xử lý chậm hơn bình thường.

Tưởng sau khi thiết lập xong cả 2 giới hạn là có thể ngủ ngon nhưng không, một vấn đề mới lại xuất hiện.

Vấn đề mới

Mỗi lần pm2 khởi động lại dịch vụ, PID của tiến trình bị thay đổi theo. Theo lẽ thường tìm cách cố định PID nhưng đó là điều bất khả thi vì nó được cấp phát ngẫu nhiên. cpulimit ngoài cấu hình theo PID thì còn cấu hình được theo một vài tiêu chí như đường dẫn của tệp thực thi nhưng trong các lần thử nghiệm đều không thành công. Tưởng đâu đi vào bế tắc thì nhớ ra pm2 có một tính năng nâng cao gọi là PM2 API.

PM2 API là tập hợp các API của pm2 giúp can thiệp vào công cụ quản lý tiến trình này. Một trong số đó là khả năng lắng nghe sự kiện của các tiến trình đang chạy trên pm2. Hiểu đơn giản có thể coi nó như là hook. Mỗi sự kiện phát ra đều có thể lắng nghe và thực thi nhiệm vụ liên quan. Áp dụng vào trường hợp này, mỗi khi dịch vụ khởi động lại, lắng nghe và chạy lại lệnh cpulimit để đặt lại giới hạn.

Cách làm rất đơn giản, bạn đọc có thể tham khảo tệp js mà tôi viết như sau.

const pm2 = require("pm2");
const { spawn } = require("child_process");
const fs = require("node:fs");

const PM_CONFIGURATIONS = [{ pm_id: 1, cpu_limit: "80" }];

pm2.connect((err) => {
  if (err) {
    console.error("PM2 connect error:", err);
    process.exit(2);
  }

  pm2.launchBus((err, bus) => {
    console.log("PM2 launchBus");

    if (err) {
      console.error("PM2 launchBus error:", err);
      process.exit(2);
    }

    bus.on("process:event", (data) => {
      // Chỉ xét event start hoặc restart
      if (!["start", "restart", "online"].includes(data.event)) return;

      let pid = null;
      const { pm_id, name } = data.process;
      pid = data.process.pid;
      if (!pid) {
        // lấy pid từ file log pm_pid_path
        const pm_pid_path = data.process.pm_pid_path;
        const pm_pid = fs.readFileSync(pm_pid_path, "utf8");
        pid = pm_pid;
      }
      console.log(`Event=${data.event} name=${name} pm_id=${pm_id} pid=${pid}`);

      // Tìm cấu hình tương ứng
      const config = PM_CONFIGURATIONS.find((config) => config.pm_id === pm_id);

      if (config) {
        // Gắn cpulimit nếu tìm thấy cấu hình
        console.log(`→ Áp cpulimit ${config.cpu_limit}% cho PID=${pid}`);
        spawn("cpulimit", ["-p", pid, "-l", config.cpu_limit, "-b"]);
      } else {
        // Không làm gì nếu không tìm thấy cấu hình
        console.log(`→ Bỏ qua pm_id=${pm_id}`);
      }
    });
  });
});

PM_CONFIGURATIONS chứa thông tin cấu hình các dịch vụ cần được lắng nghe để mỗi khi khởi động lại, thực hiện tìm kiếm PID mới được gán cho nó và dùng lệnh cpulimit để giới hạn CPU.

Tổng kết

Qua bài viết này, tôi đã chia sẻ cách tối ưu hóa và kiểm soát tài nguyên dịch vụ trên PM2 trong môi trường hạn chế tài nguyên. Đầu tiên, việc giới hạn bộ nhớ với tham số --max-memory-restart giúp giảm thiểu nguy cơ tràn bộ nhớ và treo máy chủ, đảm bảo dịch vụ tự động khởi động lại khi cần thiết. Tuy nhiên, khi vấn đề CPU tăng cao xuất hiện, giải pháp bổ sung là sử dụng công cụ cpulimit để giới hạn mức sử dụng CPU cho từng tiến trình cụ thể. Dù vậy, việc PID thay đổi mỗi lần dịch vụ khởi động lại đã đặt ra một thách thức mới.

Để khắc phục, tôi đã tận dụng PM2 API để tự động lắng nghe các sự kiện như khởi động hoặc khởi động lại dịch vụ, từ đó cập nhật PID và gán lại cpulimit một cách tự động. Đây không chỉ là một cách tiếp cận thực tế mà còn là một gợi ý hữu ích cho những ai gặp bài toán tương tự. Hy vọng bài viết sẽ giúp ích cho bạn trong việc xử lý các vấn đề liên quan đến quản lý tài nguyên trên pm2.

Cao cấp
Hello

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...