Hiện tượng nghẽn cổ chai đơn nhân trên các máy chủ lưu lượng cao
Bạn có một máy chủ với 64 nhân CPU và uplink 10Gbps, nhưng hiệu năng mạng vẫn bị giới hạn. Nếu chạy lệnh top khi lưu lượng tăng cao, bạn có thể thấy một cảnh tượng lạ: CPU0 bị chiếm dụng 100% ở cột “si” (ngắt phần mềm), trong khi 63 nhân còn lại gần như đang nhàn rỗi. Đây chính là hiện tượng nghẽn cổ chai đơn nhân điển hình.
Theo mặc định, nhiều Card mạng (NIC) xử lý tất cả các gói tin đến trên một ngắt CPU duy nhất. Khi lưu lượng đạt mức 1 triệu gói tin mỗi giây (pps) hoặc cao hơn, một nhân duy nhất không thể theo kịp việc xử lý ngắt. Kernel bắt đầu làm rơi (drop) các gói tin, dẫn đến độ trễ tăng vọt trên 200ms và lỗi timeout kết nối. Giải quyết vấn đề này không cần phần cứng mới; bạn chỉ cần hướng dẫn Linux kernel cách phân phối tải đó.
Tìm hiểu các giải pháp: RSS, RPS và RFS
Linux cung cấp ba công cụ chính để phân bổ việc xử lý mạng trên cấu trúc CPU của bạn. Lựa chọn đúng tùy thuộc vào việc phần cứng của bạn có đảm nhận được công việc nặng nhọc này hay không, hay kernel cần can thiệp thông qua phần mềm.
1. RSS (Receive Side Scaling)
RSS là phương pháp hiệu quả nhất vì nó diễn ra ở cấp độ phần cứng. NIC sử dụng nhiều hàng đợi nhận (receive queues) phần cứng. Khi một gói tin đến, NIC tính toán một mã băm (hash) dựa trên địa chỉ IP và cổng (bộ 4 tham số – 4-tuple). Sau đó, nó gán gói tin đó vào một hàng đợi cụ thể gắn với một nhân CPU nhất định. Quá trình này diễn ra trước cả khi gói tin đi tới Linux kernel.
2. RPS (Receive Packet Steering)
RPS là giải pháp thay thế dựa trên phần mềm cho RSS. Đây là “cứu cánh” cho các môi trường ảo hóa, như các instance AWS EC2 t3 hoặc phần cứng cũ chỉ cung cấp một hàng đợi RX duy nhất. Khi một nhân nhận được gói tin, RPS sẽ tính toán mã băm và chuyển giao gói tin cho các CPU khác để xử lý tiếp. Mặc dù việc này tiêu tốn một lượng nhỏ chu kỳ CPU cho quá trình chuyển giao, nhưng nó ngăn chặn một nhân duy nhất trở thành điểm nghẽn của toàn bộ hệ thống.
3. RFS (Receive Flow Steering)
RFS cải tiến dựa trên RPS bằng cách giúp nó nhận biết được ứng dụng. Thay vì chỉ phân bổ gói tin ngẫu nhiên, RFS theo dõi nhân nào mà ứng dụng của bạn (như Nginx hoặc Redis) thực sự đang sử dụng. Sau đó, nó điều hướng các gói tin đến đúng nhân đó. Điều này giúp tăng đáng kể tỷ lệ truy xuất bộ nhớ đệm L1/L2 (cache hits), giảm độ trễ do việc di chuyển dữ liệu giữa các bộ nhớ đệm CPU khác nhau.
So sánh các phương pháp
| Tính năng | RSS | RPS | RFS |
|---|---|---|---|
| Triển khai | Phần cứng (NIC) | Phần mềm (Kernel) | Phần mềm (Kernel) |
| Tốt nhất cho | Máy chủ vật lý | Máy ảo / NIC đơn hàng đợi | Ứng dụng nhạy cảm với độ trễ |
| Chi phí CPU | Thấp nhất | Thấp đến trung bình | Trung bình |
| Hiệu quả | Cao | Vừa phải | Tối ưu tính cục bộ bộ nhớ đệm |
Thứ tự ưu tiên tối ưu hóa
Việc mở rộng ngăn xếp mạng của máy chủ thường tuân theo một trình tự logic. Hãy bắt đầu với các hàng đợi phần cứng, sau đó lấp đầy các khoảng trống bằng điều hướng phần mềm.
- Tối đa hóa RSS: Nếu NIC của bạn hỗ trợ 8 hàng đợi nhưng bạn có 16 nhân, hãy cấu hình đủ 8 hàng đợi trước.
- Xếp lớp RPS: Sử dụng RPS để bù đắp khoảng cách giữa hàng đợi phần cứng và tổng số nhân. Ví dụ: sử dụng RPS để phân phối 8 hàng đợi phần cứng đó trên tất cả 16 nhân hiện có.
- Tinh chỉnh với RFS: Kích hoạt RFS nếu bạn đang chạy các tác vụ cơ sở dữ liệu hoặc máy chủ web có độ truy cập đồng thời cao, nơi mỗi micro giây độ trễ bộ nhớ đệm đều quan trọng.
Hướng dẫn triển khai
Bước 1: Cấu hình hàng đợi phần cứng (RSS)
Kiểm tra khả năng phần cứng của bạn bằng công cụ ethtool. Bạn cần xem driver của mình hỗ trợ bao nhiêu kênh “Combined”.
# Kiểm tra thiết lập hàng đợi hiện tại và tối đa
sudo ethtool -l eth0
Nếu giá trị “Combined” hiện tại thấp hơn mức tối đa, hãy tăng nó lên để khớp với số nhân CPU của bạn (hoặc mức tối đa mà phần cứng cho phép):
# Tăng số hàng đợi lên 8
sudo ethtool -L eth0 combined 8
Bước 2: Cấu hình điều hướng phần mềm (RPS)
RPS sử dụng một bitmask để xác định CPU nào có thể xử lý gói tin. Mask này là biểu diễn thập lục phân (hexadecimal) của các nhân CPU. Ví dụ: mask f (nhị phân 1111) sử dụng các nhân 0-3, trong khi ff sử dụng các nhân 0-7.
# Kích hoạt RPS trên hàng đợi 0 cho 8 nhân đầu tiên (hex ff)
echo "ff" | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
Nếu NIC của bạn có nhiều hàng đợi nhận (rx-0, rx-1, v.v.), bạn phải áp dụng mask này cho mọi thư mục hàng đợi để đảm bảo phân phối đồng đều.
Bước 3: Kích hoạt điều hướng ứng dụng (RFS)
RFS yêu cầu cấu hình hai cấp. Đầu tiên, hãy đặt giới hạn toàn cục cho tổng số luồng (flow) mà kernel nên theo dõi. Đối với một máy chủ web bận rộn, 32.768 là một điểm khởi đầu tốt.
sudo sysctl -w net.core.rps_sock_flow_entries=32768
Tiếp theo, thiết lập số lượng luồng cho từng hàng đợi phần cứng riêng lẻ. Giá trị này thường được tính bằng total_entries / number_of_queues. Đối với thiết lập một hàng đợi, hãy để nó khớp với giá trị toàn cục:
# Thiết lập số lượng luồng cho hàng đợi đầu tiên
echo "32768" | sudo tee /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
Xác minh kết quả
Đừng vội cho rằng các thiết lập đã hoạt động chỉ vì các lệnh không trả về lỗi. Bạn cần quan sát hành vi thực tế của CPU khi có tải. Sử dụng mpstat để theo dõi quá trình phân phối ngắt phần mềm theo thời gian thực.
# Theo dõi tất cả CPU mỗi 1 giây
mpstat -P ALL 1
Quan sát cột %soft. Trước khi thực hiện những thay đổi này, CPU0 có khả năng sẽ ở mức 90-100% trong khi các nhân khác ở mức 0%. Sau khi cấu hình thành công, bạn sẽ thấy các giá trị %soft được phân bổ trên tất cả các nhân trong mask của mình, ví dụ khoảng 10-15% mỗi nhân.
Lời kết
Phân phối gói tin hiệu quả không chỉ là về băng thông; đó còn là về tính ổn định của hệ thống. Khi một nhân bị quá tải bởi các ngắt, kernel thường gặp khó khăn trong việc điều phối các tác vụ quan trọng khác, dẫn đến hiện tượng chập chờn (jitter). Bằng cách triển khai RSS, RPS và RFS, bạn đảm bảo ngăn xếp mạng của mình mở rộng tuyến tính với phần cứng, cho phép máy chủ xử lý lưu lượng lớn mà không gặp trở ngại nào.

