Các cổng VPN tải cao xử lý đồng thời hàng chục nghìn đường hầm mã hóa thường đối mặt với một hiện tượng nghẽn cổ chai nghiêm trọng: sự mất cân bằng tải giữa các lõi vi xử lý. Một máy chủ đa lõi sở hữu 32 hoặc 64 lõi CPU có thể chỉ ghi nhận tổng mức sử dụng vi xử lý dưới 25%, nhưng vẫn xảy ra tình trạng rớt gói tin hàng loạt trong giờ cao điểm. Khi kiểm tra các chỉ số tài nguyên, quản trị viên thường phát hiện lõi CPU 0 đang gánh 100% tải ngắt phần mềm (ksoftirqd/0 và NET_RX_SOFTIRQ), trong khi toàn bộ các lõi CPU lân cận hoàn toàn nhàn rỗi.

Nút thắt xử lý này bắt nguồn từ phương thức phần cứng mạng và cơ chế đóng gói đường hầm tương tác với hệ điều hành. Khi kỹ thuật phân phối phần cứng Receive Side Scaling (RSS) không thể băm đều các gói tin đường hầm bị bao bọc, Linux cung cấp hai giải pháp tăng tốc phần mềm mạnh mẽ: Receive Packet Steering (RPS) và Receive Flow Steering (RFS). Cấu hình chuẩn xác hai cơ chế này cho phép các hệ thống VPN mở rộng năng lực xử lý gói tin trên toàn bộ các lõi phần cứng sẵn có, đồng thời duy trì tính định vị bộ nhớ đệm vi xử lý.

Điểm nghẽn đường hầm: Sự sụp đổ đơn hàng đợi tại luồng vào VPN

Các card giao tiếp mạng vật lý (NIC) phân chia các khung dữ liệu gửi đến qua nhiều hàng đợi nhận phần cứng bằng thuật toán Receive Side Scaling. Card mạng tính toán giá trị băm Toeplitz dựa trên tiêu đề gói tin—thông thường là bộ tứ gồm địa chỉ IP nguồn, IP đích, cổng nguồn và cổng đích—để gắn luồng dữ liệu vào một bộ đệm vòng nhận (RX ring buffer). Mỗi vòng nhận kích hoạt một tín hiệu ngắt phần cứng MSI-X độc lập được ghim vào một lõi CPU cụ thể.

Hạn chế của phần cứng RSS đối với lưu lượng VPN đóng gói:
[ Máy khách từ xa ] ---> [ Gói tin UDP WireGuard / IPsec / RiceGuard ]
                                    |
                                    v
                     [ Cổng đích duy nhất (ví dụ UDP 51820) ]
                                    |
                       [ Băm Toeplitz trên card mạng ]
                                    |
       =================================================
       Mọi luồng đều rơi vào hàng đợi RX 0 (Một ngắt MSI-X)
       =================================================
                                    |
                                    v
       [ Lõi CPU 0: 100% ksoftirqd / NET_RX_SOFTIRQ ] ---> Quá tải bộ đệm vòng!
       [ Các lõi CPU 1-31: 0% tải ngắt mềm ]

Khi hàng nghìn người dùng kết nối tới bộ tập trung VPN qua các giao thức đường hầm như WireGuard, IPsec hoặc các giao thức dữ liệu chuyên dụng, toàn bộ luồng gói tin lớp ngoài đều trỏ về một cổng đích duy nhất trên cổng chuyển tiếp. Nếu nhiều luồng lưu lượng của người dùng đến từ cùng một cổng chuyển đổi địa chỉ mạng (Carrier-Grade NAT) của nhà mạng trung gian, giá trị băm bộ tứ sẽ thu hẹp cục bộ vào một số lượng rất ít hàng đợi phần cứng.

Ngoài ra, các giao diện mạng ảo (tun hoặc tap) mặc định không có cơ chế đa hàng đợi phần cứng. Khi cổng VPN giải mã xong tải trọng và chuyển các gói tin IP nội bộ vào ngăn xếp mạng của nhân hệ điều hành, tiến trình xử lý ngắt mềm dồn nén toàn bộ áp lực lên chính lõi vi xử lý đang thực thi luồng giải mã. Khi các bộ đệm vòng không được giải phóng kịp thời, độ trễ phân vị cao tăng vọt và tình trạng thất thoát gói tin thầm lặng gây suy giảm chất lượng cuộc gọi thời gian thực cũng như các phiên truyền phát dữ liệu.

Receive Packet Steering: Cơ chế phân phối đa lõi bằng phần mềm

Receive Packet Steering đảm nhận nhiệm vụ san sẻ gánh nặng xử lý gói tin sang một mặt nạ các lõi CPU do quản trị viên chỉ định sau khi giai đoạn ngắt phần cứng kết thúc. Khác với RSS phần cứng vốn vận hành hoàn toàn trên vi mạch của card mạng, RPS hoạt động bằng phần mềm trong chu kỳ thăm dò nhận gói NAPI của trình điều khiển (netif_receive_skb).

Kiến trúc điều phối phần mềm RPS:
[ Gói tin đến trên hàng đợi nhận RX 0 ]
                   |
                   v
       [ Quy trình thăm dò NAPI ]
                   |
  [ Mặt nạ CPU rps_cpus: ví dụ 0x0000ffff (Lõi 0 đến 15) ]
                   |
     [ Tính toán hàm băm jhash trên bộ tứ thông tin ]
                   |
                   v
  [ Đưa sk_buff vào hàng đợi backlog input_pkt_queue của CPU mục tiêu ]
                   |
  [ Gửi ngắt liên vi xử lý (IPI): kích hoạt NET_RX trên lõi mục tiêu ]
                   |
     +-------------+-------------+-------------+
     |             |             |             |
     v             v             v             v
  [Lõi 0]       [Lõi 1]       [Lõi 2]       [Lõi 3] ... [Lõi 15]

Khi một gói tin xuất hiện, trình điều khiển kiểm tra xem tính năng RPS có được bật trên hàng đợi giao diện đó hay không thông qua hệ thống tệp sysfs. Nếu được kích hoạt, nhân hệ điều hành sẽ băm tiêu đề mạng và tầng giao vận bằng thuật toán Jenkins (jhash). Giá trị băm tính toán được sẽ ánh xạ trực tiếp đến một trong các lõi CPU được cho phép trong chuỗi mặt nạ nhị phân rps_cpus.

Lõi CPU tiếp nhận ban đầu sẽ đặt bộ mô tả sk_buff vào hàng đợi chờ xử lý cục bộ (input_pkt_queue) của lõi CPU đích và phát tín hiệu ngắt liên vi xử lý (Inter-Processor Interrupt - IPI). Lõi đích sau đó tiếp nhận và xử lý gói tin qua các tầng giao thức phía trên: áp dụng quy tắc tường lửa, tra cứu bảng định tuyến và chuyển giao cho socket.

Receive Flow Steering: Bảo toàn bộ nhớ đệm và liên kết luồng

Mặc dù RPS phân bổ đồng đều tải trọng tính toán trên nhiều lõi vi xử lý, cơ chế này chỉ lựa chọn lõi đích dựa trên thuật toán băm gói tin đơn thuần. RPS hoàn toàn không biết luồng tiến trình không gian người dùng đang đọc dữ liệu từ socket đang chạy trên lõi vi xử lý nào.

Nếu lõi CPU 4 xử lý gói tin mạng qua ngăn xếp nhân, nhưng tiến trình daemon VPN lại vận hành luồng xử lý trên lõi CPU 12, toàn bộ dữ liệu tải trọng phải trung chuyển qua ranh giới bộ nhớ đệm giữa các chip. Quá trình di chuyển dữ liệu xuyên lõi này gây ra lỗi trượt bộ nhớ đệm L1/L2, vô hiệu hóa các đường dẫn nhớ đệm và làm giảm băng thông truy xuất bộ nhớ hệ thống.

Receive Flow Steering giải quyết triệt để sự lãng phí này bằng cách định tuyến tiến trình xử lý gói tin trực tiếp đến đúng lõi CPU nơi tiến trình ứng dụng đang tích cực thăm dò hoặc đọc dữ liệu từ socket.

Cơ chế liên kết luồng RFS:
1. Luồng ứng dụng trên Lõi 3 gọi lệnh recvmsg(sock_fd)
   ---> Nhân cập nhật Bảng luồng toàn cục (rps_sock_flow_table):
        [ Mã băm luồng #A18C -> Đang chạy trên Lõi 3 ]

2. Gói tin tiếp theo của luồng #A18C đến hàng đợi card mạng
   ---> Trình điều khiển tra cứu rps_sock_flow_table:
        Lõi mục tiêu = Lõi 3 (Nơi dữ liệu bộ nhớ đang sẵn sàng!)

3. sk_buff được đưa thẳng vào input_pkt_queue của Lõi 3
   ---> Ngắt mềm và ứng dụng cùng chia sẻ hệ thống bộ nhớ đệm L1/L2!

RFS phối hợp quản lý hai bảng dữ liệu nội bộ trong nhân:

  1. Bảng luồng socket toàn cục (rps_sock_flow_table): Tự động được ghi nhận bất cứ khi nào luồng ứng dụng thực thi các lệnh gọi hệ thống như recvmsg(2), read(2) hoặc epoll_wait(7) trên một socket. Nhân lưu lại giá trị băm của luồng cùng định danh lõi CPU của luồng đang gọi.
  2. Bảng luồng theo hàng đợi (rps_flow_table): Được duy trì riêng biệt trên từng hàng đợi nhận của giao diện mạng. Bảng này lưu giữ ID của lõi CPU đã thực hiện xử lý gói tin gần nhất của mỗi luồng dữ liệu.

Trước khi chuyển hướng gói tin đến lõi CPU do bảng socket toàn cục chỉ định, RFS đối chiếu dữ liệu với bảng của hàng đợi cục bộ. Nếu ứng dụng vừa chuyển vùng sang một lõi CPU mới, RFS chỉ thực hiện chuyển giao tiến trình xử lý sau khi toàn bộ gói tin đang chờ trên hàng đợi của lõi cũ đã được xử lý xong. Biện pháp này ngăn chặn hoàn toàn hiện tượng đảo lộn thứ tự gói tin trong luồng truyền.

Hướng dẫn thiết lập: Triển khai RPS và RFS trên máy chủ VPN

Quy trình tối ưu hóa dưới đây áp dụng cho máy chủ VPN Linux 16 lõi với card mạng vật lý bên ngoài eth0 và giao diện đường hầm WireGuard wg0.

Bước 1: Gán mặt nạ CPU cho cơ chế RPS

Trước tiên, quản trị viên phân bổ các lõi vi xử lý chịu trách nhiệm điều phối gói tin. Trên hệ thống có 16 lõi (từ CPU 0 đến CPU 15), chuỗi mặt nạ thập lục phân 0xffff sẽ kích hoạt toàn bộ các lõi. Để đảm bảo tính ổn định, có thể dành riêng CPU 0 và CPU 1 cho các tác vụ hệ thống và ngắt phần cứng, phân chia các lõi từ CPU 2 đến CPU 15 (mặt nạ 0xfffc) cho việc xử lý gói tin:

# Bật tính năng RPS cho các lõi 2-15 trên toàn bộ hàng đợi RX của eth0
for rx_queue in /sys/class/net/eth0/queues/rx-*; do
    echo "fffc" > "$rx_queue/rps_cpus"
done

# Kích hoạt RPS trên giao diện đường hầm ảo
echo "fffc" > /sys/class/net/wg0/queues/rx-0/rps_cpus

Bước 2: Thiết lập dung lượng bảng RFS toàn cục và theo hàng đợi

Tiếp theo, cấu hình kích thước bảng luồng dựa trên số lượng kết nối đồng thời tối đa dự kiến. Với máy chủ phục vụ 32.768 phiên kết nối khách cùng lúc, quản trị viên chọn giá trị lũy thừa của 2 gần nhất là 65.536:

# Cấu hình dung lượng bảng luồng socket toàn cục
sysctl -w net.core.rps_sock_flow_entries=65536

# Phân bổ số mục luồng trên từng hàng đợi nhận vật lý (4 hàng đợi: 65536 / 4 = 16384)
for rx_queue in /sys/class/net/eth0/queues/rx-*; do
    echo 16384 > "$rx_queue/rps_flow_cnt"
done

Bước 3: Mở rộng hàng đợi nhận dữ liệu tồn đọng

Trong các đợt bùng nổ lưu lượng tức thời với mật độ gói tin dày đặc, hàng đợi tồn đọng của lõi CPU đích có thể bị tràn nếu tiến trình ngắt mềm xử lý chậm hơn tốc độ gửi tới. Nâng cao tham số netdev_max_backlog giúp loại bỏ hoàn toàn hiện tượng rớt gói được ghi nhận trong /proc/net/softnet_stat:

# Mở rộng giới hạn hàng đợi tồn đọng mỗi lõi từ 1000 lên 10000 gói tin
sysctl -w net.core.netdev_max_backlog=10000

# Tăng hạn mức xử lý trong mỗi phiên thăm dò NAPI
sysctl -w net.core.netdev_budget=600
sysctl -w net.core.netdev_budget_usecs=4000

Kiến trúc đồng bộ hóa trong công nghệ RiceGuard VPN

RiceGuard, động cơ định tuyến tầng 3 được phát triển độc quyền bởi VPNRice, tích hợp trực tiếp với các cơ chế điều hướng gói tin cấp nhân của Linux. Thay vì để hệ điều hành lập lịch ngẫu nhiên, RiceGuard chủ động điều phối nhóm luồng xử lý đồng bộ với cấu trúc phần cứng máy chủ:

  1. Ghim lõi tối ưu hóa NUMA: RiceGuard phân lập các luồng xử lý ngắt phần cứng, khối mã hóa mật mã và vòng lặp socket I/O vào các nút NUMA riêng biệt. Khi lưu lượng đến trên một card mạng vật lý kết nối với nút NUMA 0, hệ thống chỉ kích hoạt rps_cpus trên các lõi vi xử lý dùng chung bộ nhớ đệm L3 với đường truyền PCI Express đó.
  2. Đồng bộ hóa bảng luồng liên tục: Trong quá trình tiếp nhận lượng lớn kết nối mới, các tiến trình socket của RiceGuard gửi gợi ý định vị trực tiếp vào bảng luồng nhân, duy trì tính tương thích bộ nhớ đệm tối đa giữa không gian người dùng và các ngắt mềm trong nhân.
  3. Kết quả đo lường thực tế: Trong các thử nghiệm tải nặng trên đường truyền 10 Gbps với 25.000 phiên kết nối đồng thời, việc đồng bộ hóa RPS và RFS đã giảm độ trễ phân vị p99 từ 8,4 ms xuống còn 1,9 ms, đồng thời triệt tiêu hoàn toàn sự cố nghẽn ngắt mềm.

Câu hỏi thường gặp

Sự khác biệt cốt lõi giữa RSS, RPS và RFS là gì?

RSS là công nghệ trên phần cứng card mạng, sử dụng các hàng đợi vật lý và ngắt MSI-X để phân bổ gói tin. RPS là giải pháp phần mềm trong nhân Linux giúp phân phối tải nhận gói tin sang nhiều lõi sau khi hoàn tất ngắt phần cứng. RFS là bước nâng cấp của RPS, có khả năng định tuyến gói tin đến chính xác lõi vi xử lý đang vận hành tiến trình ứng dụng sở hữu socket đích.

Kích hoạt RPS có làm tăng mức sử dụng CPU không?

RPS tạo ra một lượng phụ tải tính toán rất nhỏ khoảng 1% đến 2% CPU do chi phí băm Jenkins và phát các tín hiệu ngắt liên vi xử lý IPI. Tuy nhiên, bằng cách giải tỏa tình trạng nghẽn đơn lõi và chia đều công việc, cơ chế này giúp nâng cao đáng kể ngưỡng thông lượng tối đa của toàn bộ máy chủ.

Có thể áp dụng RPS và RFS cho giao diện ảo tun/tap không?

Hoàn toàn có thể. Các giao diện ảo tun và tap hưởng lợi rất lớn từ RPS. Do các thiết bị ảo này thông thường chỉ sở hữu một hàng đợi duy nhất, việc kích hoạt rps_cpus trên rx-0 giúp phân bổ luồng xử lý giải mã gói tin sang các lõi CPU còn lại, loại bỏ triệt để điểm nghẽn đường hầm.