作者:陳俊浩
pps:一種機關,表示每秒封包數。
核:本文中說到的核,是指processor。
ring:DPDK實作的核間通訊用的高速環形緩沖區。
RSS特性:根據ip、tcp或者udp元組資訊計算hash,将封包分發給hash值對應編号的核的一種網卡特性。
mbuf結構:DPDK用來管理封包的結構體。
sk_buff結構:核心協定棧用來管理封包的結構體。
ospf協定:一種動态路由協定,目前主要用于TGW的容災功能上。
numa:非統一記憶體通路的簡稱,是一種消除CPU通路記憶體時對前端總線的競争的架構。
實體核:實體上的 processor。
邏輯核:超線程模拟的 processor。
socket:本文特指CPU socket,而非網絡socket。
BPF:柏克萊封包過濾器,一種通過指定的規則快速比對過濾封包的接口。
perf:linux自帶的一種性能分析工具。
TGW是一套實作多網接入的負載均衡系統,為騰訊業務提供着外網接入服務。随着TGW影響力的提升,越來越多的業務接入TGW,對于TGW的整體負載能力要求也越來越高,性能問題也逐漸成為TGW的痛點。
其中,最突出的問題,就是單台機器轉發性能隻有140萬pps,跑不滿10Gb流量,造成機器資源浪費。另外,一些pps高、流量大、又無法擴容的叢集,要經常在較大壓力下運作,也給業務帶來不穩定因素。
是以,提升單機的轉發性能,充分利用CPU、記憶體與網卡,成為TGW性能優化的關鍵。
請輸入标題 abcdefg
做性能優化,首先要分析瓶頸:
1.規則表、連接配接表等都是多核間的共享資源,讀寫都加鎖,容易造成較大cache-misses。
2.頁面小,目前隻有4KB,而TGW的連接配接池需要占用30GB左右的記憶體,就容易造成大量的TLB miss。
解決方案
做完瓶頸分析,就來思考解決方案:
1.要消除共享資源加鎖,首先想到的方案是無鎖化,每個處理封包的核都能自己維護一份資源,盡量減少cache-misses。
2.要消除TLB-misses,則可以采用hugepage,使用2M甚至1G的頁面。
綜上兩點,我們選擇了基于DPDK的開源解決方案來改造TGW,原因如下:
(1)DPDK實作了多線程/多程序封包處理架構,為TGW資源per-cpu化提供便利。
(2)DPDK實作了基于hugepage的記憶體池管理,為TGW連接配接池、規則表等通路優化提供了便利。
(3)DPDK實作了高效的ring接口,為封包零拷貝操作提供了便利。
(4)DPDK實作了網卡隊列映射到使用者态,TGW可以改造成為應用程式,在使用者态處理封包,少走了核心網絡協定棧的部分邏輯,降低與核心的耦合。
當然,業界也有其他的解決方案,比如netmap,為啥就選擇DPDK呢?原因主要有2點:
(1)netmap仍然采用中斷,當pps高時,中斷容易打斷本來正在處理封包的CPU工作,影響吞吐;而DPDK預設采用輪詢,CPU自己判斷網卡隊列是否有封包了,不打斷CPU工作。
(2)netmap仍避免不了使用系統調用,而系統調用時需要切換上下文,勢必造成CPU cache-misses,無法發揮CPU極緻性能。而DPDK都在使用者态實作,消除了系統調用的開銷。
做設計過程中,我們遇到了各種各樣的問題:
1.使用哪種封包處理模型?
答:使用DPDK改造網絡轉發程式,需要确定每個核負責的工作以及核與核之間的互動,設計好封包處理模型。
DPDK的example程式中,提供了run-to-completion以及pipeline兩種模型。
run-to-completion是指從開始處理封包起,到封包發出去,都是由某個核負責。這種模型讓編碼變得簡單,每個核跑同樣的邏輯,可以靈活地做平行擴充。
pipeline是指将封包處理邏輯拆分成多個段,每個邏輯段跑在獨立的核上,當封包跑完一個邏輯段,就通過核間的ring,将封包丢給另一個核,跑另一個邏輯段。這種模型有利于充分利用CPU cache的局部性原理,避免頻繁重新整理cache。
對于TGW而言,run-to-completion模型無法滿足功能需求,因為TGW采用tunnel模式,需要解析ipip封包,将外層ip頭部剝離,取出内層ip位址計算hash并進行分發,以保證出入方向的封包都可以跑到同一個業務邏輯處理核上。而完全的pipeline模型實作起來比較複雜,代碼改動量大(利用DPDK改造之前,TGW更接近run-to-completion模型),容易出bug,影響穩定性。
最終,采用了兩者結合的一種模型:
(1)封包分發核,從網卡接收隊列收取封包,根據源目标ip位址計算hash(若是收到ipip封包,則剝離掉外層ip頭部,利用内層ip位址計算hash),然後通過ring,将其分發給對應的業務邏輯處理核。
(2)業務邏輯處理核,對封包進行查找規則、連接配接、封裝解封裝ipip封包等處理,然後将封包塞入網卡發送隊列,發送出去。
我們做了以下模拟測試:
根據測試結果,得出以下結論:
(1)跨socket的組合性能最低。
(2)純實體核的組合比實體核跟邏輯核混搭的組合性能高。
(3)封裝轉發的邏輯比較重,可以通過增加核來提高性能。
是以,盡量使用同個socket的實體核,就可以有更高性能。
但是,理想總是美好的,現實卻是如此殘忍。
經統計,TGW總共需要使用35GB記憶體(主要是業務邏輯處理用到)。
TGW主流的機器隻有64GB記憶體,2個socket,假設取其中56GB挂載hugepage(留6GB左右記憶體給系統使用),如果采用1G大小的hugepage,則每個socket最多可以使用28GB記憶體(linux做了限制,必須均分),那麼業務邏輯處理核需要跨socket。如果采用2M大小的hugepage,可以調整每個socket使用記憶體的比例,但是需要配置好numa政策,增加了與作業系統的耦合,并且TLB-misses機率會相對大一些。
權衡利弊,最終選擇了1G大小的hugepage,用一些跨socket導緻的性能消耗,換來與作業系統的解耦以及TLB-misses機率的降低。
1.選擇多線程還是多程序?
答:多線程與多程序差別主要是位址空間獨立與否。另外,多程序挂了一個程序,還有其他程序可以繼續服務;多線程一旦挂了,就全部線程都會退出。
TGW是通過ospf協定來實作叢集容災的,一台機器挂了,上聯交換機一旦探測到這台機器沒有響應,則會将封包發往叢集中的其他機器,不會再發往這台挂掉的機器了。
如果TGW采用多程序,某個程序挂了,其他程序仍然繼續工作,此時上聯交換機的探測封包很可能依然可以探測成功(活着的程序處理了探測封包),交換機依然會把業務封包發往這台機器。此時,TGW需要将死掉的程序排除在外,不将業務封包給它處理,否則業務封包會丢失。這樣,TGW就要再做一層程序間的容災,增加了系統複雜性,且帶來的收益不大。
是以,TGW采用了多線程。
2.DPDK采用是輪詢封包的方式,CPU會長期100%,如何确定機器負載以及是否已經到達性能極限了呢?
答:在業務封包處理的路徑上,封包分發核跟業務邏輯處理核是主要的參與者。若封包分發核負載高,則網卡接收隊列的占用率會随之升高。而業務邏輯處理核負載高,則它與封包分發核之間的ring占用率也會随之升高。是以,對于機器負載的确定,TGW采用監控網卡接收隊列以及兩種核之間的ring的占用率,替代監控CPU占用率。
3.脫離了核心,需要自己實作arp學習、動态路由、ssh登入等基礎功能嗎?
答:TGW沒有完全脫離核心,僅僅是讓業務封包在使用者态程式中處理,非業務封包都采用DPDK提供的kni功能,丢給核心處理。是以,arp學習、動态路由、ssh登入的非業務封包都會被扔給核心處理。
4.kni是什麼?
答:kni是DPDK實作的與核心協定棧做封包互動的接口,其中包括一個ko子產品與相應的通訊接口。
ko子產品主要做以下兩件事:
(1)啟動一個核心線程,核心線程負責接收從使用者态發來的封包,并将其從mbuf結構轉換成sk_buff結構,再調用netif_rx來讓該封包跑核心協定棧。
(2)在核心注冊一個虛拟網絡接口,若應用程式通過socket發封包,在核心準備通過虛拟網絡接口發出去時,會調用kni注冊的發送函數,封包将被轉換成mbuf結構,并被丢到與使用者态程式通訊的ring中。
kni工作原理如下圖:
1.kni建立的是虛拟網絡接口,那真實的網絡接口怎麼處理,如eth0、eth1?
答:TGW把eth0、eth1都幹掉了,而kni建立的虛拟網絡接口名稱就改為eth0、eth1。這樣可以保持對一些依賴于網絡接口名稱的腳本或者程式的相容性。
2.kni會不會影響業務流量統計功能?
答:會!由于業務封包是不走kni接口的,是以ifconfig統計的流量已經不準确了。好在DPDK提供了擷取網卡流量的接口,是以TGW依然可以擷取到網卡流量。
3.怎麼實作類似tcpdump功能?
答:tcpdump是将過濾條件轉換成BPF的規則,下發給核心,核心利用這些規則過濾封包,再将比對條件的封包上傳到使用者态。
但是,BPF比較複雜,移植到TGW的難度較大,是以TGW采用另一種方案:
(1)實作一個工具,該工具将過濾條件傳到TGW封包處理子產品。然後,該工具再執行tcpdump,将指定的過濾條件,轉換成BPF規則,下發到核心。
(2)在TGW封包處理子產品這邊,從網卡收取到封包後,以及将封包轉發出去之前,利用工具傳過來的簡單過濾條件(隻比對ip、端口、傳輸層協定),進行比對。
(3)對于符合簡單過濾條件的封包,則clone一份,将clone結果通過kni接口,發往核心。這裡的clone,隻是申請一個新的mbuf結構體,引用原始封包,并不會做内容拷貝。而在封裝ipip封包的時候,則會做類似于核心copy-on-write政策的操作。
(4)核心協定棧收到封包,根據之前tcpdump下發的BPF規則,過濾封包,将封包送往使用者态,最終由tcpdump列印出來。
4.怎麼打日志?
答:打日志需要寫檔案,如果直接在業務邏輯處理核列印日志,那麼會影響封包處理。于是,TGW采用了以下方案,解決業務邏輯核列印日志的問題:
(1)維護專用的日志記憶體池,記憶體池中每個節點,都是一塊日志緩沖區。
(2)調用日志接口時,會從記憶體池申請一個節點,日志資訊直接寫到該節點上,并将該節點塞入ring中(這裡的ring是專門用于傳送日志的,與傳輸封包用的ring是互相獨立的)。
(3)控制面線程從ring中讀取日志資訊,并寫入檔案。
完成了基于DPDK的前期改造,經過測試,TGW的極限性能隻有320萬 pps,僅僅比原來版本提高一倍。于是,我們在目前基礎上,對TGW進行了調優。
1.多核擴充
測試發現,當跑到320萬 pps時,TGW有大量丢包,丢包原因在于網卡接收隊列滿了,說明是封包分發核性能不足。
目前,TGW采用的是2個封包分發核與8個業務邏輯處理核的組合,每個網口僅對應着1個封包分發核。
由此看來,1個網口隻由1個封包分發核來收取分發封包,顯然是不夠的。根據之前選核測試得出的結論:增加核數,可以提高業務處理性能,我們嘗試調整了封包分發核的核數,并做了以下極限性能測試:
根據測試結果,可以得出,8個封包分發核與8個業務邏輯核的組合是性能最好的,但是,由于機器隻有24個核,除去kni、同步、控制面線程獨占的核外,隻剩17個核。如果采用性能最好的方案,則系統隻剩下1個核用了,整個系統會長期處于CPU高負荷狀态。是以,經過評估,我們采用了4個封包分發核與8個業務邏輯核的組合。既保留給系統足夠的CPU資源,又可以提升TGW性能到600萬 pps。
2.新機型
盡管經過多核擴充後,TGW仍然隻可以跑到600萬 pps。後來,新機型出來了,CPU是intel E5 (48核),128GB記憶體,40Gb網卡。
于是,又做了以下極限性能測試:
3.單核優化
從之前的測試結果來看,有2個問題:
(1)當業務邏輯核數增加到14個之後,成功收取封包數下降了,說明是封包分發核的性能不足了。
(2)當業務邏輯核數增加到12個之後,成功轉發封包數下降了,說明業務邏輯核的性能不足了。
那有沒有辦法繼續提高性能呢?
根據perf結果,分析代碼,發現有3個問題:
a.封包分發核會将一些TGW的自定義資料存在mbuf結構的第2條cache line,該條cache line并沒有提前預取,在寫資料時,就引起了cache-misses。
b.接近極限性能的時候,mbuf占用率很高,懷疑是否mbuf的記憶體池太小了(當時隻有32768)。
c.之前做多核擴充的時候,為了圖友善,沒有将封包分發核與業務邏輯核之間的ring兩兩獨立開,而是每個網口對應的封包分發核共享與業務邏輯核數相當的ring,這樣封包分發核對ring的通路就需要做互斥同步了,也會産生cache-misses。
針對上述的問題,分别做了以下優化:
(1)裁減TGW的自定義資料,把沒必要的字段去掉,并将其位置改到第0條cache line中。
(2)将mbuf記憶體池大小擴大為131072。
(3)每個封包分發核跟每個業務邏輯核都有一一對應的ring,保證對ring的操作隻有單寫單讀。
加上上述優化後,極限性能測試結果如下:
從測試結果來看,8個封包分發核與16個業務邏輯核的組合的性能最高。另外,綜合該組合的測試結果看,單核優化前後對比,封包分發核的極限處理性能可以提高700萬pps,業務邏輯核的極限處理性能可以提高350萬pps。
開發過程中,我們也遇到一些坑:
1.詭異的丢包
TGW上線後,我們遇到了一個問題,就是網卡的統計計數中,imissed一項會增加,這意味着封包分發核的性能不足。但是,當時TGW負載不高,出入封包量遠遠沒到達性能極限。
剛開始,懷疑是封包分發核之間共享ring,産生競争導緻的。
于是,将每個網口對應的封包分發核數臨時改成1個,消除封包分發核之間的資源競争。測試結果發現,現象有所緩解,丢包率峰值從4.8%降到0.2%。
繼續排查,通過pidstat檢視TGW各個線程的運作情況,發現封包分發核的任務被動排程次數較多,并且不定時會有突發。然後,觀察任務排程次數突發與封包丢棄的關系,發現一旦出現突發,丢棄的封包數就升上去了。是以,可以确定,封包丢棄給任務被動排程有關系,懷疑是任務被排程出去了,然後封包處理不過來,就給丢了。
于是,我們通過嘗試設定實時程序的方式來解決這個問題。設定實時程序,提高TGW線程的優先級,避免TGW的線程任務被排程出去。設定實時程序後,封包丢棄的問題确實得到了解決。
但是,跑了一段時間後,卻發現了一個新的問題:系統上出現了大量D狀态的程序。檢視進入D狀态的調用棧發現,卡在了flush_work上(如下圖所示)。出現D狀态程序的原因是TGW被設定為SCHED_FIFO的實時程序,且其線程是不會主動退出的或者産生主動排程的,而實時程序的優先級本來就大于kworker的優先級,導緻核心程序kworker一直得不到排程,進而其他程序的I/O相關操作得不到處理,進入了D狀态。
由此看來,設定實時程序的方式還是太暴力了,不能采用。
網上搜尋資料,發現核心參數isolcpus+中斷親和性設定可以實作CPU獨占,任務不會被排程出去。馬上測試一下,發現封包丢棄現象有所好轉,但未完全根治。在另一個機型的機器上測試,卻沒有發現封包丢棄現象。
難道封包丢棄跟機器硬體有關系?
檢視dmesg,發現有這種日志:
觀察發現,列印日志的時候,就會出現封包丢棄現象。
再次網上搜尋資料,發現有人遇過類似的問題,并給出了解決方案:
https://jasonlinux.wordpress.com/2013/12/30/performance-regression-and-power-limit-notification-on-dell-poweredge/
這個是linux kernel不能很好地相容dell伺服器電源管理特性(測試用的機器,恰好就是dell R620),可以通過設定核心參數(clearcpuid=229)來解決。采用該方案再次測試,已經沒有出現封包丢棄現象了。終于完整地解決這個封包丢棄問題了。
2.DEBUG下的core dump
由于使用了kni接口,若程式直接退出,怕會引用的一些資源沒有釋放而導緻問題。是以在停止TGW之前,加入了rte_eth_dev_stop來停止網卡。
但是,也由此發現了一個DPDK的代碼BUG:
若網卡采用向量收封包模式,并且開啟了CONFIG_RTE_LIBRTE_MBUF_DEBUG,調用rte_eth_dev_stop,則一定機率上會出現core dump。
分析代碼,發現原因如下:
(1)向量收封包模式下,mbuf結構轉交給封包分發核處理後,其指針仍然留在網卡接收隊列中,并沒有清掉。封包轉發出去後,mbuf結構會被網卡驅動給釋放掉。
(2)調用了rte_eth_dev_stop時,會周遊網卡接收隊列,将其中所有mbuf結構給釋放掉,結果将之前已經轉發出去的封包對應的mbuf結構再次釋放一遍,造成二次釋放。
(3)開啟CONFIG_RTE_LIBRTE_MBUF_DEBUG時,釋放mbuf結構的代碼中會判斷,是否已經釋放過了,如果已經釋放過,則産生panic,進而産生core dump。
最終,這個問題報給了intel的工程師。而我們采用了去掉TGW停止網卡的代碼,并關閉CONFIG_RTE_LIBRTE_MBUF_DEBUG選項的方法來規避解決問題。
優化後的TGW,已經上線了一年多了。從線上機器運作情況來看,優化效果還是相當明顯的。以前需要4台機器來抗住壓力的叢集,現在用2台就可以了,節省了機器資源,也解決了高負載叢集的問題。
文章來自:騰訊架構師