从C10K到C10M高性能网络的探索与实践
原文:https://blog.qiniu.com/archives/4941
優化整理:極客重生
hi,大家好,今天是大年初一,先祝大家新年快樂!今天依然是干貨,性能優化大局觀,高端食材,值得品嘗。
在高性能網絡的場景下,C10K是一個具有里程碑意義的場景,15年前它給互聯網領域帶來了很大的挑戰。發展至今,我們已經進入C10M的場景進行網絡性能優化。這期間有怎樣的發展和趨勢?圍繞著各類指標分別有哪些探索和實踐?12月20日在北京舉辦的“七牛架構師實踐日”沙龍中,來自京東的資深架構師閆國旗為大家做了題為“從C10K到C10M高性能網絡的探索與實踐”的分享,更多網絡分享參考:五個半小時。
以下是對他演講內容的文字實錄。
C10K時代的問題與優化手段
首先帶大家回顧一下當年C10K場景中遇到的問題以及為了解決我們單機下高并發的承載能力所做的改進。在當時的年代,國內互聯網的普及程度相對較低,C10K并沒有給當時中國的互聯網環境帶來太大沖擊,但是在全球互聯網環境下大家開始意識到這個問題。為了解決該問題,首先的研究方向就是IO模型的優化,逐漸解決了C10K的問題。
epoll、kqueue、iocp,io_ring就是IO模型優化的一些最佳實踐,這幾種技術實現分別對應于不同的系統平臺。以epoll為例,在它的基礎上抽象了一些開發框架和庫,為廣大軟件開發者在軟件開發帶來了便利,比如libevent、libev等。隨著當年在IO模型上的革命,衍生出了很多至今為止我們都在大量使用的優秀開源軟件,比如nginx、haproxy、squid等,通過大量的創新、實踐和優化,使我們在今天能夠很輕易地解決一個大并發壓力場景下的技術問題。
深入理解Linux異步I/O框架 io_uring
這里簡單列了幾點,較為常用的優化技術手段。
CPU親和性&內存局域性
目前我們使用的服務器主要是多路、多核心的x86平臺。用于運行我們的軟件代碼,在很多場景的業務需求下,都會涉及一定并發任務,無論是多進程模型還是多線程模型,都要把所有的調度任務交給操作系統,讓操作系統幫我們分配硬件資源。我們常用的服務器操作系統都屬于分時操作系統,調度模型都盡可能的追求公平,并沒有為某一類任務做特別的優化,如果當前系統僅僅運行某一特定任務的時候,默認的調度策略可能會導致一定程度上的性能損失。我運行一個A任務,第一個調度周期在0號核心上運行,第二個調度周期可能就跑到1號核心上去了,這樣頻繁的調度可能會造成大量的上下文切換,從而影響到一定的性能。
數據局域性是同樣類似的問題。當前x86服務器以NUMA架構為主,這種平臺架構下,每個CPU有屬于自己的內存,如果當前CPU需要的數據需要到另外一顆CPU管理的內存獲取,必然增加一些延時。所以我們盡可能的嘗試讓我們的任務和數據在始終在相同的CPU核心和相同的內存節點上,Linux提供了sched_set_affinity函數,我們可以在代碼中,將我們的任務綁定在指定的CPU核心上。一些Linux發行版也在用戶態中提供了numactl和taskset工具,通過它們也很容易讓我們的程序運行在指定的節點上。
RSS、RPS、RFS、XPS
這些技術都是近些年來為了優化Linux網絡方面的性能而添加的特性,RPS、RFS、XPS都是Google貢獻給社區,RSS需要硬件的支持,目前主流的網卡都已支持,即俗稱的多隊列網卡,充分利用多個CPU核心,讓數據處理的壓力分布到多個CPU核心上去。RPS和RFS在linux2.6.35的版本被加入,一般是成對使用的,在不支持RSS特性的網卡上,用軟件來模擬類似的功能,并且將相同的數據流綁定到指定的核心上,盡可能提升網絡方面處理的性能。XPS特性在linux2.6.38的版本中被加入,主要針對多隊列網卡在發送數據時的優化,當你發送數據包時,可以根據CPU MAP來選擇對應的網卡隊列,低于指定的kernel版本可能無法使用相關的特性,但是發行版已經backport這些特性。
詳細參考:服務器性能優化之網絡性能優化
IRQ 優化
關于IRQ的優化,這里主要有兩點,第一點是關于中斷合并。在比較早期的時候,網卡每收到一個數據包就會觸發一個中斷,如果小包的數據量特別大的時候,中斷被觸發的數量也變的十分可怕。大部分的計算資源都被用于處理中斷,導致性能下降。后來引入了NAPI和Newernewer NAPI特性,在系統較為繁忙的時候,一次中斷觸發后,接下來用輪循的方式讀取后續的數據包,以降低中斷產生的數量,進而也提升了處理的效率。第二點是IRQ親和性,和我們前面提到了CPU親和性較為類似,是將不同的網卡隊列中斷處理綁定到指定的CPU核心上去,適用于擁有RSS特性的網卡。
這里再說說關于網絡卸載的優化,目前主要有TSO、GSO、LRO、GRO這幾個特性,先說說TSO,以太網MTU一般為1500,減掉TCP/IP的包頭,TCP的MaxSegment Size為1460,通常情況下協議棧會對超過1460的TCP Payload進行分段,保證最后生成的IP包不超過MTU的大小,對于支持TSO/GSO的網卡來說,協議棧就不再需要這樣了,可以將更大的TCPPayload發送給網卡驅動,然后由網卡進行封包操作。通過這個手段,將需要在CPU上的計算offload到網卡上,進一步提升整體的性能。GSO為TSO的升級版,不在局限于TCP協議。LRO和TSO的工作路徑正好相反,在頻繁收到小包時,每次一個小包都要向協議棧傳遞,對多個TCPPayload包進行合并,然后再傳遞給協議棧,以此來提升協議棧處理的效率。GRO為LRO的升級版本,解決了LRO存在的一些問題。這些特性都是在一定的場景下才可以發揮其性能效率,在不明確自己的需求的時候,開啟這些特性反而可能造成性能下降。
Kernel 優化
關于Kernel的網絡相關優化我們就不過多的介紹了,主要的內核網絡參數的調整在以下兩處:net.ipv4.*參數和net.core.*參數。主要用于調節一些超時控制及緩存等,通過搜索引擎我們能很容易找到關于這些參數調優的文章,但是修改這些參數是否能帶來性能的提升,或者會有什么弊端,建議詳細的閱讀kernel文檔,并且多做一些測試來驗證。
更深入的探索和實踐
接下來,我們著重了解如何去更進一步提升我們單機網絡吞吐以及網絡處理性能的技術和手段。計算機硬件做為當前IT發展的重要組成部分。作為軟件開發者,我們更應該掌握這部分的內容,學習了解我們的軟件如何在操作系統中運行,操作系統又怎樣分配我們的硬件資源。
硬件
CPU
CPU是計算機系統中最核心、最關鍵的部件。在當前的x86服務器領域我們接觸到主要還是Intel的芯片。索性我們就以IntelXeon 2600系列舉例。
Intel Xeon 2600系列的CPU已經發布了3代,第4代產品2016年Q1也即將面市,圖例中均選取了4代產品最高端的型號。圖一為該系列CPU的核心數量統計,從第一代的8核心發展到即將上市的22核心,若干年前,這是很可怕的事情。裝配該型號CPU的雙路服務器,再開啟超線程,輕而易舉達到80多個核心。就多核處理器的發展歷程來講,核心數量逐年提升,主頻基本穩定在一定的范圍內,不是說單核主頻不再重要,而是說在當前的需求場景下,多核心才是更符合我們需求的處理器。
圖1
不僅僅是核心數量,像LLC緩存的容量、內存帶寬都有很大的提升,分別達到了55MB和76.8GB/s。
內存
關于內存,可能它的發展歷程并沒有像CPU或者其他硬件這樣耀眼奪目。可能大家更關心的就是價格吧。目前在服務器領域,DDR3內存仍是主流,DDR4內存因為成本等問題并沒有大面積普及。這里列舉了IDF15的一些數據,從Intel的銷售市場調研報告來看,在明年Q2左右會看到更多的服務器CPU支持DDR4,但是PC機的普及可能還需要一段過渡時間。
網絡
當年我們可能僅僅使用一臺服務器就能滿足我們的業務需求,但是隨著業務規模的擴大,單臺服務器的能力已經遠不能支撐現在的業務,所謂的分布式擴展,便被大家推了上現,所以現在的業務對網絡的依賴越來越高。關于網絡硬件,我們也以Inter系列的網卡來舉例,總結一下目前比較成熟的特性,像RSS特性,前面也提到了,這個特性是需要硬件支持的,目前大部分中小企業可能還是千兆網絡為主,像82559這類的網卡,都已經支持了比較多的隊列。今年新出的X710芯片也是正對應著云計算或者虛擬化的需求,提供更多相關的特性,如virtualfunction,SR-IOV,tunnel protocol offload等等。隨著云計算的發展,未來包含這些特性的網卡將會成為主流。
如何更好的利用硬件特性
現在主流的硬件性能已經很強大了,但是我們的應用軟件,真的能夠充分利用這些硬件嗎?如何更進一步把這些硬件特性利用起來,我們摸索出一系列的技術的段,先聊聊比較關鍵的三點:數據包處理、任務調度和數據存儲。
首先就是如何處理我們的數據包,使其速度更快,效率更高。其次讓我們的任務按業務邏輯進行調度,而不僅僅是我起個并發模型,讓操作系統幫我們調度。第三是關于數據訪問,也就是和內存交互的一些技巧。
內核協議棧問題
在2013年在Shmoocon會議上,Robert提出”kernel不是一個萬能的解決方案,它正是一個問題所在。”這樣一個命題。隨著互聯網的發展,Linux已經成為服務器領域上不可或缺的角色。但是它的初衷并不是?針對某一類應用場景進行特殊的優化和適配,本質上來說還是一個分時系統,希望更公平地服務眾多的用戶以和任務。但正是因為它的設計目標是如此,所以在過去的發展中也主要是解決這些問題。進而所造成的一個問題就是系統越來越龐大,功能越來越多,邏輯越來越臃腫,雖然通過一些手段有的優化,但是整體的架構對于我們上層的軟件開發者來說還是比較復雜的。
圖2是從linux foundation的一篇文章kernel_flow中截取的,我們可以看到在用戶態中,從調用write()等API到將數據發送到網卡上經過了相當多的邏輯處理,在很多需求場景中,可以省略一定的邏輯上整個的東西,復雜的實現,一定程度上對軟件開發者的理解造成了障礙。
圖2
這里重點講兩個瓶頸點,第一個就是全局的隊列,在我們在寫用戶態網絡程序中,對同一個網絡端口,僅允許一個監聽實例,接收的數據包由一個隊列來維護,并發的短連接請求較大時,會對這個隊列造成較大的競爭壓力,成為一個很大瓶頸點,至少在linuxkernel 3.9版本之前是這樣,在3.9的版本合并了一個很關鍵的特性SO_REUSEPORT,支持多個進程或線程監聽相同的端口,每個實例分配一個獨立的隊列,一定程度上緩解這個問題。用更容易理解的角度來描述,就是支持了我們在用戶態上對一個網絡端口,可以有多個進程或線程去監聽它。正是因為有這樣一個特性,我們可以根據CPU的核心數量來進行端口監聽實例的選擇,進一步優化網絡連接處理的性能。
第二點也是一個比較大的問題,在linuxkernel中有一個全局的連接表,用于維護TCP連接狀態,這個表在維護大量的TCP連接時,會造成相當嚴重的資源競爭。總的來說,有鎖的地方,有資源占用的地方都可能會成為瓶頸點。
高性能網絡
對于前面提到的問題,是我們為了實現高性能網絡優先要解決的。目前,在業界來說解決這些問題主要有以下兩套方案。第一套方案就是說更進一步在linuxkernel中優化網絡協議棧處理的業務邏輯和結構,但是這里會有一個問題,前提你要有一個相對有一定kerne經驗的團隊做這件事情,而且對它足夠了解,但是對于大部分企業來說是很難具備這樣的一個團隊的。另外一個解決方案,讓你的數據包接近用戶態,盡可能的和kernel少做交互,目前我們選擇的是第二種方式,盡可能不讓kernel做太多事情,至少在網絡數據包處理這塊。
關于網絡數據包處理這塊,如何讓它更接近用戶態,主要有兩點。第一點,在收到數據包之后不進協議棧,把數據包的內存直接映射到用戶態,讓我們的程序在用戶態直接可以看到這些數據。這樣就繞過了kernel的處理。第二個其這間利用了linuxUIO,這個特性叫UIO,比如當前流行的DPDK,通過這個模塊框架我們可以在驅動程序收到數據包之后,直接放到用戶態的內存空間中,也同樣達到了繞過協議棧的目的。
高性能網絡的問題
大家也可能想到一個問題,我雖然繞過了Linux協議棧,數據包直接達到用戶態,對于大部分比較業務應用來說,沒有太大的意義,為什么?因為此時應用程序看到的數據包僅僅是內存中的一段二進制,獲取IP等信息,不知如何獲取,和原有的socket編程方式完全不兼容。在一些特殊專用領域的,它們可能會有一些特有的流程來處理這些數據,而且不需要協議棧的支持,例如IDS這類功能。所以我們需要這樣一個協議棧,并且支持傳統的Socket框架,避免帶來應用程序修改成本。我們知道協議棧從BSD等系統最初的實現,發展到現在,經歷了幾十個年頭,功能越來越復雜,規模越來越龐大。像前面提到采用繞過kernel默認協議棧的方案的話,協議棧是永遠不可避免的。無論是購買一個商業的協議棧,移植開源協議棧,或者自己重新實現。
用戶態協議棧
現在一些大公司都已經進行了相關的嘗試,并且帶到了生產環境中。對于協議棧,還是有一些優化的空間,就TCP協議來說,它的場景其實是針對不可控的互聯網而產生的,它所面臨的問題也主要是延時不同,距離不同等等廣域網的場景,比如滑動窗口,TCP擁塞算法等等。對于現在的互聯網業務而言,每套業務系統都有很多的業務層次,除了接入層需要直接面向互聯網和用戶打交道,其余大量的數據交互都發生在機房內部或機房之間,就算跨兩個異地的機房,之間的網絡延時也不過幾毫秒,并不像互聯網的環境那樣的復雜,也正因如此,很多特性在這些場景中看來有些“多余”。也正是因為這些特性,對于網絡性能而言也是有一定的損耗,也給我們更多的空間,根據我們自己的業務對其進行定制和優化。
擴展性
這個也是大家都面臨的一個問題。例如,數據流量進來,需要對它進行采樣統計分析,或者做一些更精細化的流量調度,如此眾多的業務需求,如果像前面提到的在linuxkernel內部做優化,做擴展的話,挑戰還是蠻大的。正是因為我們有如此多的業務需求,所以我們選擇了在kernel之外做這樣的事情。
我們的服務器經常會有一種現象,CPU0 使用率很高,但大部分的CPU核心很空閑,我們在編程的時候,多進程模型或多線程模型,只是描述程序的并發模型,和系統的多核調度并沒有太多直接的聯系。這些調度模型是否能夠充分利用多核,就像前面提到的CPU親和性,將某個進程或線程綁定在指定的CPU核心,充分利用CPU核心的緩存,減少在上下文切換等。這里產生了另外一個問題,雖然這種方式是能解決對于CPU多核利用率不佳的問題,但這種硬性關聯無法解決我們業務上的需求,也就是說結合業務,我們更了解應該怎樣調度我們計算資源和數據。
剛剛提到了多進程和多線程,有些同學說用協程的模型也可以來解決這個問題吧?但協程只是讓開發者更容易使用的一種封裝,在進程內模擬并發,并自行管理上下文,比系統提供的要輕一些,但最終還是落在現在的調度模型上。這里有一個比較有爭議的建議,不見得在任何場景中都適用。
cache優化
前面已經提到過,對于在一條網絡連接上的數據處理,盡可能保持在一個CPU核心上,以此換取CPUCache的利用率。網絡連接流保持在一個核心上,可以降低在網絡連接流上處理的優化成本,如果背道而馳的話,可能我接收數據包的是CPU0核心,發送數據包的是CPU1核心,這樣可能會引入業務處理上的復雜度。
無鎖數據結構,其是更多的都是編程上的一些技巧,雖然我們用的是多核平臺,盡可能讓每個核心做自己的事情,但是不可避免在業務的需求上還是有跨CPU核心或實例的通信過程,這類通信是無法避免的,我們只有盡可能保證不要有鎖的出現,不是用鎖解決資源問題不好,而是這種上下文中,解決數據一致性可能成本會頗高,得不償失,特別是在本文提到的需求場景下。
保證你的數據結構盡可能在相同的CPU核心上進行處理,對于一個數據包相關的數據結構或者哈希表,在相同的類型實例上都保存一份,雖然增加了一些內存占用,但降低了資源沖突的概率。
圖3是一些常見的延時,其中一部分的數據是在我的laptop上測試得出的,另外一部分是引用JeffDean。我們可以看到對于一個CPU寄存器或緩沖的訪問,基本都在1個時鐘周期內就可以完成了,延時是很低的,對于L1緩存來說至少需要4個時鐘周期,LLC至少要26個時鐘周期,到我們內存訪問的話就是更是拉開相當大的數量級。也是說明前面為什么多次提到盡可能充分利用CPU的緩存,而不是說盡可能把數據放在內存里,因為內存訪問的代價,在這里看來有些昂貴的,的確是這樣的。但是說對于IO設備,之間的差距則變得更大了。
圖3
內存優化
前面提到的都是關于任務調度以及關于對數據包處理上的優化的關鍵點,最后這一點主要關于內存的技巧。第一個是盡可能的不要使用多級的指針嵌套,怎么理解呢?在我們傳統中實現,都會抽象很多結構體,通過我們的業務抽象出很多層次,然后通過指針一級級關聯下去,從這個角度看沒有什么問題,通過指針可以很方便對數據進行索引和處理,避免數據處理的拷貝。但最大的問題也是發生在這里,多級的指針檢索,有很大的機率觸發你的CacheMiss。對于網絡數據處理,盡可能將你的數據結構以及數據業務層面盡可能抽象更加扁平化一些,這是一個取舍的問題,也是在高性能面前尋求一個平衡點。
Hugepage主要解決的問題就是TLB Miss的問題。TLB是需要靠硬件實現的,因為成本很高,所以它的容量沒有像其他的存儲呈指數級的增長。但是我們的內存幾十G,上百G都已經是常態了。這種不對稱的存在,造成了大內存在4k頁面的時候產生了大量的TLB Miss。目前解決的手段就是使用更大的內存頁,也就是hugepage,雖然可以換來性能提升,但是也引入了另外一個問題,就是內存利用率的問題,同時要求你對大頁內存的使用進行數據結構上的優化。
最后一個內存預分配的問題,其實在一些軟件中可以看到它的影子,像一些數據庫的實現,我們可以看到預分配內存的場景,它的引入解決的問題是什么呢?如果需要內存,進行數據存儲的時候,頻繁去申請釋放內存,在內存管理就會把整體的性能拉下來,提前進行內存預分配,可以一定程度上降低這方面的開銷。還是說要對內存進行更精細化的管理,避免在內存分配上引入一些性能損失或容量損失等等。
總結
前面提到了這些都是我們在做高性能網絡框架這個項目中遇到的一些問題,在業內交流汲取的經驗,以及我們在實踐上所帶來的一些積累。就目前整體的IT產業的發展,無論軟件還是硬件來說,它的發展在向一個目標前進,軟件開發人員需要了解硬件的工作原理,硬件接觸較多的同學比如運維也要更了解你的業務是怎樣的實現,軟硬結合會是未來持續方向之一。雖然說在當前這個云計算大潮下,很多軟件人員只需要把代碼寫好,直接發布出去了,這樣對你的開發是帶來一定便利性。但也逃避不了這樣的一個角色,幫助你讓你的軟件更好的運行在底層平臺上。
8086剛問世的那個時代,主CPU是沒有浮點運算的,要用攜處理器來支持。隨著歷史發展,所有功能都在向CPU內遷移,就像FSB,逐漸取消了,內存管理功能移到了CPU內部,這是CPU整個處理器產業的發展。當然對我們來說是很好的,我們可以使用更大的帶寬在CPU和內存之間進行交互。我國近幾年對于網絡安全的發展尤其看重,比如全站HTTPS,我們知道這種場景的話需要很大計算資源進行加密解密,但是這部分的運算讓通用CPU來做的話,性能并不是特別理想,所以又引入了的協處理器的概念,當然已經不是當年的那個協處理器,而用一塊輔助的硬件加速卡幫我們做SSL Offload的事情。使用GPU進行加速處理,這個也是我們調研的方向,就是我們希望用GPU這樣一個比較優秀的并行架構能夠幫助我們在網絡處理上,可能達到更好的效果,另外提到關于英特爾的phi卡,天河2號,連續幾年拿到超算排名,不可否認有一部分是由phi卡帶來的。
作為軟件開發人員不僅僅代碼寫的漂亮,質量高,一定要對硬件有一定的了解,做運維的同學不僅需要對硬件,對中間件,對服務器了解的同時,也同時多看看業務方有怎樣的需求,怎么樣把資源和業務結合的更好,更好的為我們的用戶提供更好的服務。
- END -
看完一鍵三連在看,轉發,點贊
是對文章最大的贊賞,極客重生感謝你
推薦閱讀
服務器性能優化之網絡性能優化
深入理解高并發服務器性能優化
深入理解緩存系統|單機QPS突破千萬優化之路
你好,這里是極客重生,我是阿榮,大家都叫我榮哥,從華為->外企->到互聯網大廠,目前是大廠資深工程師,多次獲得五星員工,多年職場經驗,技術扎實,專業后端開發和后臺架構設計,熱愛底層技術,豐富的實戰經驗,分享技術的本質原理,希望幫助更多人蛻變重生,拿BAT大廠offer,培養高級工程師能力,成為技術專家,實現高薪夢想,期待你的關注!點擊藍字查看我的成長之路。
校招/社招/簡歷/面試技巧/大廠技術棧分析/后端開發進階/優秀開源項目/直播分享/技術視野/實戰高手等,?極客星球希望成為最有技術價值星球,盡最大努力為星球的同學提供技術和成長幫助!詳情查看->極客星球
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 求點贊,在看,分享三連
總結
以上是生活随笔為你收集整理的从C10K到C10M高性能网络的探索与实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万字长文|深入理解XDP全景指南
- 下一篇: 定个目标|建立自己的技术知识体系