Quản lý tiến trình và kiểm soát tài nguyên Linux: Tối ưu hiệu suất máy chủ với nice, renice, cgroups, và systemd

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Bắt đầu nhanh: Kiểm soát các tiến trình vượt ngoài tầm kiểm soát (5 phút)

Đã bao giờ bạn thấy máy chủ Linux của mình trở nên ì ạch, với các phiên tương tác bị giật lag hoặc dịch vụ web không phản hồi? Thông thường, thủ phạm là một tiến trình hoặc một nhóm tiến trình chiếm dụng tài nguyên hệ thống như CPU hoặc bộ nhớ. Trong khi Linux xuất sắc trong việc đa nhiệm, đôi khi một tác vụ lại trở nên quá tham lam.

Thông thường, vấn đề bắt nguồn từ một tiến trình không được kiểm soát đúng mức. Các tiến trình Linux thường chia sẻ tài nguyên một cách công bằng, nhưng một tính toán ngốn CPU hoặc một ứng dụng tiêu tốn bộ nhớ có thể nhanh chóng chiếm hết dung lượng khả dụng.

Để khắc phục nhanh, bạn có thể điều chỉnh độ ưu tiên của tiến trình ngay lập tức. Linux sử dụng giá trị ‘niceness’ (dao động từ -20 đến 19, trong đó số càng thấp thì độ ưu tiên càng cao). Lệnh nice khởi chạy một tiến trình mới với độ niceness được chỉ định, trong khi renice thay đổi độ ưu tiên của một tiến trình đang chạy.

Khởi chạy một tiến trình với độ ưu tiên thấp hơn

Giả sử bạn có một tác vụ sao lưu chạy dài mà bạn không muốn nó làm gián đoạn các dịch vụ chính của mình. Bạn có thể khởi động nó với giá trị niceness cao hơn (độ ưu tiên thấp hơn):

nice -n 10 tar -zcf /tmp/website_backup.tar.gz /var/www/html

Ở đây, -n 10 cho nice biết để khởi chạy lệnh tar với giá trị niceness là 10. Nó sẽ chạy, nhưng sẵn sàng nhường thời gian CPU hơn cho các tiến trình có độ ưu tiên cao hơn khác.

Điều chỉnh độ ưu tiên của một tiến trình đang chạy

Điều gì sẽ xảy ra nếu một tiến trình đang chạy vượt ngoài tầm kiểm soát? Trước tiên, hãy xác định ID Tiến trình (PID) của nó. Ví dụ, nếu một tập脚本 Python đang tiêu tốn quá nhiều CPU:

ps aux | grep my_data_processor.py

Khi bạn có PID, bạn có thể sử dụng renice để thay đổi giá trị niceness của nó. Để giảm độ ưu tiên của nó (tăng niceness):

sudo renice -n 15 -p <PID>

Bạn sẽ cần sudo vì một vài lý do. Nó được yêu cầu khi bạn làm cho một tiến trình ‘nicer’ (gán giá trị niceness cao hơn) hoặc nếu bạn muốn đặt niceness âm (độ ưu tiên cao hơn) cho các tiến trình của riêng mình. Hãy nhớ rằng, chỉ người dùng root mới có thể gán giá trị niceness âm cho các tiến trình thuộc sở hữu của người dùng khác.

Đối với các dịch vụ được quản lý bởi systemd, bạn thậm chí có thể đặt niceness mặc định trong các tệp đơn vị của chúng. Chỉnh sửa tệp dịch vụ (ví dụ: sudo systemctl edit --full my-service.service) và thêm:

[Service]
Nice=10

Sau đó tải lại và khởi động lại dịch vụ: sudo systemctl daemon-reload && sudo systemctl restart my-service.service.

Tìm hiểu sâu: Hiểu về độ ưu tiên và giới hạn tài nguyên

Mặc dù nicerenice rất tuyệt vời để quản lý độ ưu tiên lập lịch CPU, nhưng chúng thực sự không giới hạn lượng CPU mà một tiến trình tiêu thụ. Quan trọng hơn, chúng cũng không quản lý bộ nhớ, I/O hoặc băng thông mạng. Đó chính là lúc Control Groups, hay cgroups, trở nên thiết yếu.

Vai trò của nice và renice

Giá trị ‘niceness’ ảnh hưởng đến bộ lập lịch Linux. Một tiến trình có giá trị niceness thấp hơn (ví dụ: -10) được coi là ‘ít dễ chịu’ hơn và nhận được nhiều thời gian CPU hơn. Ngược lại, giá trị niceness cao hơn (ví dụ: 19) có nghĩa là tiến trình ‘rất dễ chịu’ và nhận được ít thời gian CPU hơn. Điều này hoạt động như một cơ chế hợp tác: một tiến trình ‘dễ chịu’ sẵn sàng nhường chu kỳ CPU nếu các tiến trình khác có độ ưu tiên cao hơn yêu cầu chúng.

  • Phạm vi: -20 (ưu tiên cao nhất) đến 19 (ưu tiên thấp nhất).
  • Mặc định: 0.
  • Tác động: Chủ yếu ảnh hưởng đến việc phân bổ thời gian CPU giữa các tiến trình cạnh tranh tài nguyên.

Những lệnh này lý tưởng cho các tác vụ nền không quan trọng, cho phép bạn chạy các tiến trình mà không ảnh hưởng đáng kể đến các hoạt động chính của mình. Cuối cùng, nicerenice thúc đẩy sự công bằng trong việc chia sẻ CPU, thay vì áp đặt các giới hạn tài nguyên cứng nhắc.

Giới thiệu Control Groups (cgroups)

Vấn đề mà nice không giải quyết triệt để là *giới hạn* tài nguyên. Bạn có thể muốn đảm bảo một lượng CPU nhất định cho một dịch vụ quan trọng, hoặc ngăn chặn một bản dựng phát triển tiêu tốn tất cả RAM khả dụng. Đây chính xác là những gì cgroups giải quyết.

Control Groups (cgroups) cho phép bạn tổ chức các tiến trình thành các nhóm phân cấp. Sau đó, bạn có thể phân bổ các tài nguyên hệ thống cụ thể cho từng nhóm, như CPU, bộ nhớ, băng thông I/O và mạng. Hãy hình dung nó như việc tạo các container ảo cho các tiến trình của bạn, mỗi container có bộ ràng buộc tài nguyên nghiêm ngặt riêng. Cơ chế cốt lõi này cũng là nền tảng cho các công nghệ container hóa như Docker.

Có hai phiên bản chính: cgroups v1 và cgroups v2. Mặc dù cgroups v1 vẫn được sử dụng rộng rãi, cgroups v2 mang đến một cách tiếp cận hợp nhất và tinh gọn hơn. Đối với các hệ thống Linux hiện đại, systemd phụ thuộc rất nhiều vào cgroups. Nó sử dụng chúng để quản lý các dịch vụ và phiên người dùng, điều này đơn giản hóa đáng kể việc áp dụng và sử dụng chúng.

systemd và cgroups: Một sự hợp tác mạnh mẽ

systemd, hệ thống khởi tạo và quản lý dịch vụ trên hầu hết các bản phân phối Linux hiện đại, vốn dĩ sử dụng cgroups để quản lý các tiến trình mà nó khởi động. Mỗi dịch vụ, slice, scope và phiên người dùng trong systemd đều có cgroup riêng. Thiết kế này giúp việc áp dụng giới hạn tài nguyên trực tiếp trong các tệp đơn vị systemd của bạn trở nên đơn giản.

Sự tích hợp này là một yếu tố thay đổi cuộc chơi. Thay vì tương tác thủ công với các tệp cgroup thô (có thể phức tạp), bạn có thể khai báo các ràng buộc tài nguyên một cách khai báo trong tệp đơn vị systemd. Ví dụ, để giới hạn CPU và bộ nhớ của một dịch vụ:

# /etc/systemd/system/my-cpu-intensive-service.service

[Unit]
Description=Dịch vụ tiêu tốn nhiều CPU của tôi
After=network.target

[Service]
ExecStart=/usr/local/bin/my_long_running_calc.sh
CPUQuota=50%
MemoryLimit=1G

[Install]
WantedBy=multi-user.target

Ở đây:

  • CPUQuota=50% đảm bảo dịch vụ này sẽ không bao giờ tiêu thụ quá 50% thời gian của một lõi CPU. Ngay cả khi hệ thống rảnh, nó cũng sẽ không vượt quá giới hạn này. Đây là một giới hạn cứng.
  • MemoryLimit=1G giới hạn dịch vụ tối đa 1 Gigabyte RAM. Nếu nó cố gắng cấp phát nhiều hơn, trình diệt tiến trình thiếu bộ nhớ (OOM killer) có thể can thiệp.

Sau khi tạo hoặc sửa đổi một tệp dịch vụ, luôn tải lại systemd và khởi động lại dịch vụ:

sudo systemctl daemon-reload
sudo systemctl enable my-cpu-intensive-service.service
sudo systemctl restart my-cpu-intensive-service.service

Phương pháp này cung cấp quyền kiểm soát chi tiết. Nó đảm bảo các dịch vụ quan trọng của bạn nhận được tài nguyên cần thiết, ngăn các ứng dụng khác chiếm dụng dung lượng hệ thống quý giá.

Sử dụng nâng cao: Kiểm soát tài nguyên chi tiết

Ngoài các giới hạn CPU và bộ nhớ cơ bản, sự tích hợp của systemd với cgroups còn cung cấp quyền kiểm soát chi tiết hơn đối với các tài nguyên hệ thống khác nhau. Điều này đặc biệt hữu ích cho các môi trường máy chủ phức tạp hoặc khi xử lý các ứng dụng có yêu cầu tài nguyên cụ thể.

Kiểm soát Disk I/O với IOWeight

I/O đĩa thường có thể là một nút thắt cổ chai. Chỉ thị IOWeight (hoặc BlockIOWeight cho cgroups v1) cho phép bạn đặt độ ưu tiên I/O cho một dịch vụ so với các dịch vụ khác. Nó tương tự như Nice nhưng dành cho truy cập thiết bị khối.

# /etc/systemd/system/my-backup-service.service

[Unit]
Description=Dịch vụ sao lưu nền của tôi

[Service]
ExecStart=/usr/local/bin/run_heavy_backup.sh
IOWeight=10     # Trọng số thấp hơn có nghĩa là độ ưu tiên I/O thấp hơn
CPUQuota=30%
MemoryLimit=512M

[Install]
WantedBy=multi-user.target

Giá trị 10 có độ ưu tiên rất thấp. Mặc định là 1000.

Giới hạn việc tạo tiến trình con với TasksMax

Một số ứng dụng có thể tạo ra nhiều tiến trình con, có khả năng làm quá tải hệ thống. TasksMax cho phép bạn đặt giới hạn trên số lượng tác vụ (tiến trình và luồng) có thể được tạo trong cgroup:

# /etc/systemd/system/my-app-server.service

[Unit]
Description=Máy chủ ứng dụng của tôi

[Service]
ExecStart=/usr/bin/my_app_server
TasksMax=100    # Giới hạn 100 tiến trình/luồng
CPUQuota=70%
MemoryLimit=2G

Dịch vụ tạm thời với systemd-run

Điều gì sẽ xảy ra nếu bạn không muốn tạo một tệp dịch vụ đầy đủ cho một lệnh một lần hoặc một thử nghiệm tạm thời? systemd-run là câu trả lời của bạn. Nó cho phép bạn chạy các lệnh tùy ý dưới dạng dịch vụ hoặc phạm vi tạm thời với các kiểm soát tài nguyên cgroup được áp dụng ngay lập tức.

Ví dụ, để chạy một bài kiểm tra áp lực CPU và bộ nhớ trong 60 giây, giới hạn nó ở 20% CPU và 256MB RAM:

sudo systemd-run --scope -p CPUQuota=20% -p MemoryLimit=256M stress --cpu 4 --timeout 60s

Cờ --scope đảm bảo nó chạy như một đơn vị ‘scope’, lý tưởng cho các lệnh ngắn hạn. Bạn có thể áp dụng bất kỳ chỉ thị tài nguyên nào mà một tệp dịch vụ thông thường chấp nhận.

Một kịch bản phổ biến khác: chạy một tác vụ hàng loạt với độ ưu tiên thấp hơn và giới hạn bộ nhớ mà không cần tệp đơn vị chuyên dụng:

sudo systemd-run --scope -p Nice=10 -p MemoryLimit=1G /usr/local/bin/long_batch_job.sh

Khả năng này mang lại sự linh hoạt to lớn cho các phiên tương tác hoặc các tập lệnh tự động, đặc biệt khi bạn cần thực thi tạm thời nhưng có kiểm soát.

Giám sát cgroups

Để xem cách cgroups của bạn được tổ chức và chúng đang tiêu thụ tài nguyên nào, bạn có thể sử dụng các công cụ như systemd-cglssystemd-cgtop (có thể cần được cài đặt, ví dụ: apt install systemd-container trên Debian/Ubuntu).

systemd-cgls

Lệnh này hiển thị toàn bộ hệ thống phân cấp cgroup. Để giám sát việc sử dụng tài nguyên cgroup theo thời gian thực:

systemd-cgtop

Những công cụ này cung cấp cho bạn cái nhìn sâu sắc về việc liệu giới hạn tài nguyên của bạn có hoạt động như mong đợi hay không và giúp chẩn đoán xung đột.

Lời khuyên thực tế: Khi nào và làm thế nào để áp dụng kiểm soát

Biết các công cụ là một chuyện; áp dụng chúng một cách hiệu quả lại là chuyện khác. Đây là cách tôi nghĩ về việc tích hợp các khái niệm này vào quản lý máy chủ hàng ngày.

nice/renice so với kiểm soát tài nguyên cgroups/systemd

  • Sử dụng nice/renice khi:
    • Bạn cần điều chỉnh nhanh chóng, tạm thời độ ưu tiên CPU của một tiến trình duy nhất.
    • Tác vụ không quan trọng và bạn chỉ muốn nó ‘dễ chịu’ hơn với các tiến trình khác.
    • Bạn đang xử lý các lệnh tương tác hoặc các tập scripts chạy một lần.
  • Sử dụng cgroups thông qua systemd (CPUQuota, MemoryLimit, v.v.) khi:
    • Bạn cần giới hạn tài nguyên cứng hoặc cấp phát được đảm bảo cho các dịch vụ quan trọng.
    • Quản lý các tác vụ nền hoặc dịch vụ chạy dài.
    • Bạn yêu cầu hiệu suất có thể dự đoán được cho các ứng dụng cụ thể.
    • Hoạt động trong môi trường máy chủ đa người dùng hoặc đa ứng dụng, nơi cách ly tài nguyên là chìa khóa.
    • Bạn muốn các chính sách tài nguyên bền vững được định nghĩa trong các tệp dịch vụ.

Xác định các tiến trình chiếm dụng tài nguyên

Trước khi bạn có thể kiểm soát tài nguyên, bạn cần biết ai đang sử dụng chúng. Các công cụ thiết yếu bao gồm:

  • tophtop: Tổng quan thời gian thực về CPU, bộ nhớ và các tiến trình.
  • ps aux: Liệt kê tiến trình chi tiết.
  • pidstat -u 1 (từ gói sysstat): Mức sử dụng CPU của từng tiến trình.
  • pidstat -r 1: Mức sử dụng bộ nhớ của từng tiến trình.
  • pidstat -d 1: Thống kê I/O của từng tiến trình.

Kinh nghiệm của tôi: Một ví dụ thực tế

Trên máy chủ Ubuntu 22.04 sản xuất của tôi với 4GB RAM, tôi từng gặp một vấn đề gây khó chịu. Một tập scripts xử lý dữ liệu hàng đêm đôi khi tăng đột biến mức sử dụng CPU và bộ nhớ, khiến các dịch vụ web của tôi không phản hồi. Ban đầu tôi đã cố gắng điều chỉnh giá trị nice cho tập scripts. Mặc dù điều này phần nào giúp ích bằng cách làm cho nó nhường CPU thường xuyên hơn, nhưng nó không hoàn toàn ngăn chặn xung đột tài nguyên, đặc biệt khi tập scripts xử lý một tập dữ liệu đặc biệt lớn.

Việc áp dụng CPUQuotaMemoryLimit trực tiếp trong tệp đơn vị systemd của tập scripts đã thay đổi tình hình. Tôi đã đặt CPUQuota=70%MemoryLimit=2G cho tác vụ hàng loạt đó. Điều này đảm bảo rằng ngay cả trong những giai đoạn hoạt động mạnh nhất, tập scripts sẽ không bao giờ làm máy chủ web của tôi bị thiếu tài nguyên hoàn toàn.

Phương pháp này đã giảm đáng kể thời gian xử lý cho các dịch vụ quan trọng khác bằng cách đảm bảo chúng luôn có đủ tài nguyên. Kết quả là một hệ thống ổn định và phản hồi nhanh hơn nhiều. Điều từng là một nỗi đau đầu hàng ngày đã trở thành một tiến trình nền tĩnh lặng mà tôi hiếm khi phải nghĩ đến.

Bắt đầu nhỏ, kiểm tra kỹ lưỡng

Khi áp dụng các giới hạn cgroup, đặc biệt là hạn ngạch bộ nhớ hoặc CPU, hãy bắt đầu với các giá trị thận trọng và giám sát hệ thống. Các giới hạn được đặt không chính xác có thể khiến các dịch vụ gặp sự cố bất ngờ. Dần dần điều chỉnh các giới hạn dựa trên mức sử dụng thực tế và kiểm tra hiệu suất.

Đừng tối ưu hóa quá mức

Mặc dù mạnh mẽ, không phải mọi tiến trình đều cần kiểm soát tài nguyên rõ ràng. Hãy tập trung vào các ứng dụng hoặc dịch vụ thực sự gây ra vấn đề về hiệu suất hoặc rất quan trọng đối với hoạt động của bạn. Các giới hạn không cần thiết đôi khi có thể làm tăng độ phức tạp mà không mang lại lợi ích đáng kể.

Bằng cách thực sự hiểu và tận dụng nice, renice, cgroups, và systemd, bạn trang bị cho mình những công cụ mạnh mẽ. Những công cụ này cho phép bạn quản lý chính xác hiệu suất máy chủ Linux của mình, đảm bảo sự ổn định và khả năng phản hồi ngay cả khi phải đối mặt với khối lượng công việc nặng.

Share: