作者:牧原
什麼是中斷?
當一個硬件(如磁盤控制器或者以太網卡), 需要打斷CPU的工作時, 它就觸發一箇中斷. 該中斷通知CPU發生了某些事情並且CPU應該放下當前的工作去處理這個事情. 為了防止多個設置發送相同的中斷, Linux設計了一套中斷請求系統, 使得計算機系統中的每個設備被分配了各自的中斷號, 以確保它的中斷請求的唯一性.
從2.4 內核開始, Linux改進了分配特定中斷到指定的處理器(或處理器組)的功能. 這被稱為SMP IRQ affinity, 它可以控制系統如何響應各種硬件事件. 允許你限制或者重新分配服務器的工作負載, 從而讓服務器更有效的工作.
以網卡中斷為例,在沒有設置SMP IRQ affinity時, 所有網卡中斷都關聯到CPU0, 這導致了CPU0負載過高,而無法有效快速的處理網絡數據包,導致了瓶頸。
通過SMP IRQ affinity, 把網卡多箇中斷分配到多個CPU上,可以分散CPU壓力,提高數據處理速度。但是smp_affinity要求網卡支持多隊列,如果網卡支持多隊列則設置才有作用,網卡有多隊列,才會有多箇中斷號,這樣就可以把不同的中斷號分配到不同CPU上,這樣中斷號就能相對均勻的分配到不同的CPU上。
而單隊列的網卡可以通過RPS/RFS來模擬多隊列的情況,但是該效果並不如網卡本身多隊列+開啟RPSRFS 來的有效
什麼是RPS/RFS
RPS(Receive Packet Steering)主要是把軟中斷的負載均衡到各個cpu,簡單來說,是網卡驅動對每個流生成一個hash標識,這個HASH值得計算可以通過四元組來計算(SIP,SPORT,DIP,DPORT),然後由中斷處理的地方根據這個hash標識分配到相應的CPU上去,這樣就可以比較充分的發揮多核的能力了。通俗點來說就是在軟件層面模擬實現硬件的多隊列網卡功能,如果網卡本身支持多隊列功能的話RPS就不會有任何的作用。該功能主要針對單隊列網卡多CPU環境,如網卡支持多隊列則可使用SMP irq affinity直接綁定硬中斷。
圖1 只有RPS的情況下(來源網絡)
由於RPS只是單純把數據包均衡到不同的cpu,這個時候如果應用程序所在的cpu和軟中斷處理的cpu不是同一個,此時對於cpu cache的影響會很大,那麼RFS(Receive flow steering)確保應用程序處理的cpu跟軟中斷處理的cpu是同一個,這樣就充分利用cpu的cache,這兩個補丁往往都是一起設置,來達到最好的優化效果, 主要是針對單隊列網卡多CPU環境。
圖2:同時開啟RPS/RFS後(來源網絡)
rps_flow_cnt,rps_sock_flow_entries,參數的值會被進位到最近的2的冪次方值,對於單隊列設備,單隊列的rps_flow_cnt值被配置成與 rps_sock_flow_entries相同。
RFS依靠RPS的機制插入數據包到指定CPU的backlog隊列,並喚醒那個CPU來執行
默認情況下,開啟irqbalance是足夠用的,但是對於一些對網絡性能要求比較高的場景,手動綁定中斷磨合是比較好的選擇
開啟irqbalance,會存在一些問題,比如:
a) 有時候計算出來的值不合理,導致CPU使用還是不均衡。
b) 在系統比較空閒IRQ處於 Power-save mode 時,irqbalance 會將中斷集中分配給第一個 CPU,
以保證其它空閒 CPU 的睡眠時間,降低能耗。如果壓力突然上升,可能會由於調整的滯後性帶來性能問題。
c) 處理中斷的CPU總是會變,導致了更多的context switch。
d)也存在一些情況,啟動了irqbalance,但是並沒有生效,沒有真正去設置處理中斷的cpu。
如何查看網卡的隊列數
1,Combined代表隊列個數,說明我的測試機有4個隊列
# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 4
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 4
2,以ecs centos7.6為例,系統處理中斷的記錄在/proc/interrupts文件裡面,默認這個文件記錄比較多,影響查看,同時如果cpu核心也非常多的話,對於閱讀的影響非常大
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 141 0 0 0 IO-APIC-edge timer
1: 10 0 0 0 IO-APIC-edge i8042
4: 807 0 0 0 IO-APIC-edge serial
6: 3 0 0 0 IO-APIC-edge floppy
8: 0 0 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 IO-APIC-fasteoi acpi
10: 0 0 0 0 IO-APIC-fasteoi virtio3
11: 22 0 0 0 IO-APIC-fasteoi uhci_hcd:usb1
12: 15 0 0 0 IO-APIC-edge i8042
14: 0 0 0 0 IO-APIC-edge ata_piix
15: 0 0 0 0 IO-APIC-edge ata_piix
24: 0 0 0 0 PCI-MSI-edge virtio1-config
25: 4522 0 0 4911 PCI-MSI-edge virtio1-req.0
26: 0 0 0 0 PCI-MSI-edge virtio2-config
27: 1913 0 0 0 PCI-MSI-edge virtio2-input.0
28: 3 834 0 0 PCI-MSI-edge virtio2-output.0
29: 2 0 1557 0 PCI-MSI-edge virtio2-input.1
30: 2 0 0 187 PCI-MSI-edge virtio2-output.1
31: 0 0 0 0 PCI-MSI-edge virtio0-config
32: 1960 0 0 0 PCI-MSI-edge virtio2-input.2
33: 2 798 0 0 PCI-MSI-edge virtio2-output.2
34: 30 0 0 0 PCI-MSI-edge virtio0-virtqueues
35: 3 0 272 0 PCI-MSI-edge virtio2-input.3
36: 2 0 0 106 PCI-MSI-edge virtio2-output.3
input0說明是cpu1(0號CPU)處理的網絡中斷
阿里雲ecs網絡中斷,如果是多箇中斷的話,還有input.1 input.2 input.3這種形式
......
PIW: 0 0 0 0 Posted-interrupt wakeup event
3,如果ecs的cpu核心非常多,那這個文件看起來就會比較費勁了,可使用下面的命令查看處理中斷的核心
使用下面這個命令,即可將阿里雲ecs處理中斷的cpu找出來了(下面這個演示是8c 4個隊列)
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
5
7
1
3
處理一下sar拷貝用
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done |tr -s '\n' ','
5,7,1,3,
#sar -P 5,7,1,3 1 每秒刷新一次cpu 序號為5,7,1,3核心的cpu使用率
# sar -P ALL 1 每秒刷新所有核心,用於少量CPU核心的監控,這樣我們就可以知道處理慢的原因是不是因為隊列不夠導致的了
Linux 3.10.0-957.5.1.el7.x86_64 (iZwz98aynkjcxvtra0f375Z) 05/26/2020 _x86_64_ (4 CPU)
05:10:06 PM CPU %user %nice %system %iowait %steal %idle
05:10:07 PM all 5.63 0.00 3.58 1.02 0.00 89.77
05:10:07 PM 0 6.12 0.00 3.06 1.02 0.00 89.80
05:10:07 PM 1 5.10 0.00 5.10 0.00 0.00 89.80
05:10:07 PM 2 5.10 0.00 3.06 2.04 0.00 89.80
05:10:07 PM 3 5.10 0.00 4.08 1.02 0.00 89.80
05:10:07 PM CPU %user %nice %system %iowait %steal %idle
05:10:08 PM all 8.78 0.00 15.01 0.69 0.00 75.52
05:10:08 PM 0 10.00 0.00 16.36 0.91 0.00 72.73
05:10:08 PM 1 4.81 0.00 13.46 1.92 0.00 79.81
05:10:08 PM 2 10.91 0.00 15.45 0.91 0.00 72.73
05:10:08 PM 3 9.09 0.00 14.55 0.00 0.00 76.36
sar 小技巧
打印idle小於10的核心
sar -P 1,3,5,7 1 |tail -n+3|awk '$NF<10 {print $0}'
看所有核心是否有單核打滿的把1357換成ALL即可
sar -P ALL 1 |tail -n+3|awk '$NF<10 {print $0}'
再貼一個4c8g規格的配置(ecs.c6.xlarge ),
可以看到4c也給了四個隊列,但是默認設置的是在cpu0 和 2上處理中斷
# grep -i "input" /proc/interrupts
27: 1932 0 0 0 PCI-MSI-edge virtio2-input.0
29: 2 0 1627 0 PCI-MSI-edge virtio2-input.1
32: 1974 0 0 0 PCI-MSI-edge virtio2-input.2
35: 3 0 284 0 PCI-MSI-edge virtio2-input.3
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
原因是cpu是超線程的,“每個vCPU綁定到一個物理CPU超線程”,所以即使是4個隊列默認也在2個cpu核心上
# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
4,關閉IRQbalance
# service irqbalance status
Redirecting to /bin/systemctl status irqbalance.service
● irqbalance.service - irqbalance daemon
Loaded: loaded (/usr/lib/systemd/system/irqbalance.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Wed 2020-05-27 14:39:28 CST; 2s ago
Process: 1832 ExecStart=/usr/sbin/irqbalance --foreground $IRQBALANCE_ARGS (code=exited, status=0/SUCCESS)
Main PID: 1832 (code=exited, status=0/SUCCESS)
May 27 14:11:40 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Started irqbalance daemon.
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopping irqbalance daemon...
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopped irqbalance daemon.
5,手動設置RPS
5.1 手動設置之前我們需要先了解下面的文件(IRQ_number就是前面grep input拿到的序號)
進入/proc/irq/${IRQ_number}/,關注兩個文件:smp_affinity和smp_affinity_list
smp_affinity是bitmask+16進制,
smp_affinity_list:這個文件更好理解,採用的是10進制,可讀性高
改這兩個任意一個文件,另一個文件會同步更改。
為了方便理解,咱們直接看十進制的文件smp_affinity_list即可
如果這一步沒看明白,注意前面的 /proc/interrupts的輸出
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
手動設置處理中斷的CPU號碼可以直接echo修改,下面就是將序號27的中斷放到cpu0上處理,一般建議可以把cpu0空出來
# echo 0 >> /proc/irq/27/smp_affinity_list
# cat /proc/irq/27/smp_affinity_list
0
關於bitmask
“f” 是十六進制的值對應的 二進制是”1111”(可以理解為4c的配置設置為f的話,所有的cpu參與處理中斷)
二進制中的每個位代表了服務器上的每個CPU. 一個簡單的demo
CPU序號 二進制 十六進制
CPU 0 0001 1
CPU 1 0010 2
CPU 2 0100 4
CPU 3 1000 8
5.2 需要對每塊網卡每個隊列分別進行設置。如對eth0的0號隊列設置:
echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
這裡的設置方式和中斷親和力設置的方法是類似的。採用的是掩碼的方式,但是這裡通常要將所有的CPU設置進入,如:
4core,f
8core,ff
16core,ffff
32core,ffffffff
默認在0號cpu上
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
0
# echo f >>/sys/class/net/eth0/queues/rx-0/rps_cpus
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
f
6,設置RFS的方式
需要設置兩個地方:
6.1, 全局表:rps_sock_flow_table的條目數量。通過一個內核參數控制:
# sysctl -a |grep net.core.rps_sock_flow_entries
net.core.rps_sock_flow_entries = 0
# sysctl -w net.core.rps_sock_flow_entries=1024
net.core.rps_sock_flow_entries = 1024
6.2,每個網卡隊列hash表的條目數:
# cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
0
# echo 256 >> /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
# cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
256
需要啟動RFS,兩者都需要設置。
建議機器上所有的網卡隊列設置的rps_flow_cnt相加應該小於或者等於rps_sock_flow_entries。因為是4個隊列,因此每個隊列設置256,可以根據實際情況增大
某壓測調整案例:
背景:iperf3 5個線程 1個1g打流量,抖動,如 4.6--5.2 波動較大,查看中斷不均衡,做如下設置
1,設置RPS ,32c 16隊列 設置ffffffff
2, RFS 65536 /16 = 4096
3, smp_affinity_list 1,3,5,7,9....31
依然不均衡,考慮到RFS 命中cache的問題,客戶端增加到16線程,中斷處理趨於穩定,但是流量依然有波動
4,關閉TSO後流量在7.9x--8.0x之間波動,趨於穩定