Linux Networking Quy mô lớn: Làm chủ IRQ Affinity, RSS và RPS

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

Giới hạn vô hình: Tại sao máy chủ bị mất gói tin khi tải thấp

Chúng tôi đang đẩy khoảng 800.000 gói tin mỗi giây (PPS) qua một API gateway thì gặp tình trạng timeout. Trên lý thuyết, máy chủ trông vẫn ổn. Băng thông chỉ ở mức khiêm tốn 300Mbps trên đường truyền 10Gbps, và tổng tải CPU trên máy 16 nhân chỉ quanh quẩn mức 15%. Tuy nhiên, netstat -s báo cáo hàng nghìn gói tin bị rớt mỗi giây.

Nhìn nhanh vào top đã tiết lộ nút thắt cổ chai. Trong khi 15 nhân khác gần như đang rảnh rỗi, thì Nhân 0 (Core 0) bị kẹt ở mức 100% trong chế độ %si (software interrupt – ngắt phần mềm). Máy chủ không gặp khó khăn về khối lượng dữ liệu; nó đang bị nghẹt bởi chi phí xử lý ngắt (interrupt overhead). Mỗi gói tin nhỏ đều kích hoạt một sự kiện mà chỉ có một nhân đang cố gắng xử lý. Nếu bạn đang mở rộng hạ tầng vượt ra ngoài vài máy ảo cơ bản, việc học cách phân phối tải này là yêu cầu bắt buộc, không còn là lựa chọn.

Bẫy đơn nhân: Hiểu về các ngắt (Interrupts)

Khi một Card mạng (NIC) nhận được một gói tin, nó sẽ báo hiệu cho CPU thông qua một Yêu cầu ngắt (IRQ – Interrupt Request). Theo mặc định, nhiều bản phân phối Linux điều hướng tất cả IRQ từ một NIC cụ thể đến CPU 0. Điều này tạo ra một rào cản hiệu năng khổng lồ.

Hãy tưởng tượng việc nhận 1,5 triệu gói tin nhỏ mỗi giây—mức thường thấy ở các microservices tần suất cao hoặc máy chủ game dựa trên UDP. Nhân đơn lẻ đó phải dừng tác vụ hiện tại 1,5 triệu lần mỗi giây để quản lý NIC. Ngay cả với bộ xử lý EPYC hoặc Xeon 64 nhân, thông lượng mạng của bạn vẫn bị giới hạn bởi tốc độ của một nhân duy nhất đó.

Mở rộng phần cứng so với phần mềm

Chúng ta giải quyết vấn đề này bằng cách phân tán khối lượng công việc. Có ba công cụ chính trong kho vũ khí của Linux:

  • RSS (Receive Side Scaling): Một tính năng phần cứng nơi NIC phân phối các gói tin vào nhiều hàng đợi (queues). Mỗi hàng đợi có IRQ riêng mà các CPU khác nhau có thể xử lý.
  • RPS (Receive Packet Steering): Phiên bản phần mềm của RSS. Kernel lấy các gói tin từ một hàng đợi phần cứng duy nhất và chuyển chúng sang các CPU khác để xử lý giao thức.
  • IRQ Affinity: Một cấu hình ràng buộc các IRQ cụ thể vào các nhân CPU cụ thể, ngăn kernel chuyển đổi tác vụ liên tục giữa các nhân làm hỏng hiệu năng bộ nhớ đệm (cache).

Bước 1: Chẩn đoán nút thắt cổ chai

Đầu tiên, hãy kiểm tra xem các ngắt của bạn có bị lệch hay không. Chạy lệnh này để theo dõi sự phân bổ trong thời gian thực:

watch -n 1 "cat /proc/interrupts | grep eth0"

(Thay thế eth0 bằng giao diện thực tế của bạn, chẳng hạn như ens3 hoặc p4p1).

Nếu các con số trong một cột CPU tăng vọt trong khi các cột khác đứng yên, bạn đang gặp vấn đề về affinity (độ ưu tiên xử lý). Bạn cũng nên kiểm tra mpstat từ gói sysstat:

mpstat -P ALL 1

Hãy tập trung vào cột %soft. Nếu một nhân hiển thị 90-100% trong khi các nhân khác hiển thị 0%, nhân đó đang bị “ngập” trong các ngắt mạng.

Bước 2: Kích hoạt RSS (Hàng đợi phần cứng)

Hầu hết các NIC 10GbE và 25GbE hiện đại đều hỗ trợ nhiều hàng đợi. Kiểm tra giới hạn phần cứng của bạn bằng ethtool:

sudo ethtool -l eth0

Nếu “Combined” hiển thị 1, nhưng “Maximum” cao hơn, bạn đang không sử dụng hết tiềm năng phần cứng của mình. Hãy tăng số hàng đợi hoạt động để khớp với số nhân của bạn (hoặc tối đa là 8 cho hầu hết các khối lượng công việc):

sudo ethtool -L eth0 combined 8

Việc tăng số lượng hàng đợi cho phép NIC giao tiếp với nhiều CPU cùng lúc.

Bước 3: Cố định IRQ Affinity

Dịch vụ irqbalance thường quản lý việc này, nhưng nó thường đưa ra các quyết định kém trên các máy chủ có lưu lượng truy cập cao. Nó thường xuyên di chuyển các ngắt giữa các nhân, điều này làm trống bộ nhớ đệm CPU và làm tăng độ trễ. Để có hiệu năng ổn định, hãy tắt nó đi.

sudo systemctl stop irqbalance
sudo systemctl disable irqbalance

Bây giờ, hãy tìm số IRQ cho các hàng đợi NIC của bạn:

grep eth0 /proc/interrupts | awk '{print $1}' | sed 's/://'

Để ánh xạ IRQ 45 vào CPU 0, bạn viết một bitmask thập lục phân vào tệp smp_affinity. Trong mask này, 1 là CPU 0, 2 là CPU 1, 4 là CPU 2, và 8 là CPU 3.

# Ràng buộc IRQ 45 vào CPU 0
echo 1 | sudo tee /proc/irq/45/smp_affinity

# Ràng buộc IRQ 46 vào CPU 1
echo 2 | sudo tee /proc/irq/46/smp_affinity

Bước 4: Sử dụng RPS cho môi trường ảo hóa

Nếu bạn đang sử dụng nhà cung cấp đám mây như AWS hoặc DigitalOcean, NIC ảo của bạn có thể chỉ hỗ trợ một hàng đợi duy nhất. Đây là lúc RPS phát huy tác dụng. Nó xử lý khối lượng công việc nặng nề của stack TCP/IP trên nhiều nhân bằng phần mềm.

Để kích hoạt RPS, hãy viết một CPU mask vào tệp rps_cpus. Đối với hệ thống 8 nhân, mask ff (nhị phân 11111111) cho phép kernel sử dụng mọi nhân.

# Kích hoạt RPS cho eth0 trên tất cả 8 nhân
echo "ff" | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus

Bước 5: Tinh chỉnh với RFS

RFS (Receive Flow Steering) đưa việc tối ưu hóa đi xa hơn. Nó theo dõi nhân nào thực sự đang chạy ứng dụng (như Nginx hoặc HAProxy) và hướng việc xử lý gói tin đến chính nhân đó. Điều này giảm thiểu việc truyền dữ liệu chéo giữa các nhân tốn kém.

Thiết lập giới hạn bảng luồng (flow table) toàn cục trước:

sudo sysctl -w net.core.rps_sock_flow_entries=32768

Sau đó, thiết lập giới hạn cho từng hàng đợi cụ thể:

echo 4096 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

Chiến lược cho môi trường thực tế (Production)

Trong các môi trường tải cao, tôi thấy rằng cách tiếp cận hỗn hợp mang lại hiệu quả tốt nhất:

  1. Tối đa hóa phần cứng: Thiết lập các hàng đợi ethtool khớp với các nhân vật lý của bạn (tối đa 8 hoặc 16).
  2. Cố định thủ công: Ràng buộc mỗi hàng đợi NIC vào nhân vật lý riêng của nó. Tránh CPU 0 nếu có thể, vì nó thường xử lý các bộ định thời hệ thống và I/O đĩa.
  3. Dự phòng bằng phần mềm: Chỉ sử dụng RPS và RFS nếu số lượng hàng đợi phần cứng ít hơn số nhân CPU của bạn.

Sau khi áp dụng những tinh chỉnh này cho API gateway của chúng tôi, chúng tôi thấy tải %si giảm từ 100% trên một nhân xuống còn 12% cân bằng trên tất cả các nhân. Độ trễ giảm mạnh 40ms trong giờ cao điểm và tình trạng mất gói tin đã biến mất. Tối ưu hóa network stack không phải là phép màu; đó là việc đảm bảo dữ liệu có một “xa lộ” nhiều làn thông thoáng để đi đến CPU.

Share: