開發與維運

關於Linux cpu中斷問題及案例

我們經常碰到整體cpu不高,但是性能不佳的案例,這種案例往往跟CPU 處理中斷的核心跑滿有關係,話不多說,我們來看看中斷相關的設置與案例。


什麼是中斷?
當一個硬件(如磁盤控制器或者以太網卡), 需要打斷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直接綁定硬中斷。
image.png

圖1 只有RPS的情況下(來源網絡)

由於RPS只是單純把數據包均衡到不同的cpu,這個時候如果應用程序所在的cpu和軟中斷處理的cpu不是同一個,此時對於cpu cache的影響會很大,那麼RFS(Receive flow steering)確保應用程序處理的cpu跟軟中斷處理的cpu是同一個,這樣就充分利用cpu的cache,這兩個補丁往往都是一起設置,來達到最好的優化效果, 主要是針對單隊列網卡多CPU環境。
image.png

圖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之間波動,趨於穩定
通過上面的設置,我們可以在實際的壓測中做一些微調測試觀察業務,也可以看看生產環境中在用的實例cpu中斷是否均衡呢?

某客戶cpu中斷不工作的案例:

背景:客戶反饋自建了幾臺代理服務器,反向代理後端的k8s集群,目前有2臺實例偶發的超時,同時後端應用沒有日誌記錄
1,客戶提供了代理服務器的抓包文件,通過抓包看到syn重傳對端沒回包,判斷問題可能在對端
image.png

2,server端抓包發現重傳報文未到server端,視線回到源實例上
3,物理機抓包排查,發現重傳報文未到物理機上,說明是系統內部出現問題
4,登陸系統內部檢查中斷的設置,這個實例是8c,8個隊列,默認設置了0246核心處理這8箇中斷
image.png

5,查看各個核心上處理中斷的數量,發現偏差較大,同時發現input6 6號中斷有問題,監控良久未變化
image.png

6,檢查系統日誌,看看input6是否有異常日誌,果不其然有個異常的告警,疑似qemu-kvm的bug
image.png

7,重啟接恢復,同時參考上面的設置,把中斷綁定到不同的核心上試試

Leave a Reply

Your email address will not be published. Required fields are marked *