作者:牧原
什麼是中斷?
當一個硬體(如磁盤控制器或者以太網卡), 需要打斷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之間波動,趨于穩定