從C10K到C10M高性能網絡的探索與實踐

在高性能網絡的場景下,C10K是一個具備里程碑意義的場景,15年前它給互聯網領域帶來了很大的挑戰。發展至今,咱們已經進入C10M的場景進行網絡性能優化。這期間有怎樣的發展和趨勢?圍繞着各種指標分別有哪些探索和實踐?12月20日在北京舉辦的「七牛架構師實踐日」沙龍中,來自京東的資深架構師閆國旗爲你們作了題爲「從C10K到C10M高性能網絡的探索與實踐」的分享,如下是對他演講內容的文字實錄。linux

C10K時代的問題與優化手段

首先帶你們回顧一下當年C10K場景中遇到的問題以及爲了解決咱們單機下高併發的承載能力所作的改進。在當時的年代,國內互聯網的普及程度相對較低,C10K並無給當時中國的互聯網環境帶來太大沖擊,可是在全球互聯網環境下你們開始意識到這個問題。爲了解決該問題,首先的研究方向就是IO模型的優化,逐漸解決了C10K的問題。nginx

epoll、kqueue、iocp就是IO模型優化的一些最佳實踐,這幾種技術實現分別對應於不一樣的系統平臺。以epoll爲例,在它的基礎上抽象了一些開發框架和庫,爲廣大軟件開發者在軟件開發帶來了便利,好比libevent、libev等。隨着當年在IO模型上的革命,衍生出了不少至今爲止咱們都在大量使用的優秀開源軟件,好比nginx、haproxy、squid等,經過大量的創新、實踐和優化,使咱們在今天可以很輕易地解決一個大併發壓力場景下的技術問題。算法

這裏簡單列了幾點,較爲經常使用的優化技術手段。數據庫

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,經過這個模塊框架咱們能夠在驅動程序收到數據包以後,直接放到用戶態的內存空間中,也一樣達到了繞過協議棧的目的。

你們也可能想到一個問題,我雖然繞過了Linux協議棧,數據包直接達到用戶態,對於大部分比較業務應用來講,沒有太大的意義,爲何?由於此時應用程序看到的數據包僅僅是內存中的一段二進制,獲取IP等信息,不知如何獲取,和原有的socket編程方式徹底不兼容。在一些特殊專用領域的,它們可能會有一些特有的流程來處理這些數據,並且不須要協議棧的支持,例如IDS這類功能。因此咱們須要這樣一個協議棧,而且支持傳統的Socket框架,避免帶來應用程序修改爲本。咱們知道協議棧從BSD等系統最初的實現,發展到如今,經歷了幾十個年頭,功能愈來愈複雜,規模愈來愈龐大。像前面提到採用繞過kernel默認協議棧的方案的話,協議棧是永遠不可避免的。不管是購買一個商業的協議棧,移植開源協議棧,或者本身從新實現。

如今一些大公司都已經進行了相關的嘗試,而且帶到了生產環境中。對於協議棧,仍是有一些優化的空間,就TCP協議來講,它的場景實際上是針對不可控的互聯網而產生的,它所面臨的問題也主要是延時不一樣,距離不一樣等等廣域網的場景,好比滑動窗口,TCP擁塞算法等等。對於如今的互聯網業務而言,每套業務系統都有不少的業務層次,除了接入層須要直接面向互聯網和用戶打交道,其他大量的數據交互都發生在機房內部或機房之間,就算跨兩個異地的機房,之間的網絡延時也不過幾毫秒,並不像互聯網的環境那樣的複雜,也正因如此,不少特性在這些場景中看來有些「多餘」。也正是由於這些特性,對於網絡性能而言也是有必定的損耗,也給咱們更多的空間,根據咱們本身的業務對其進行定製和優化。

擴展性,這個也是你們都面臨的一個問題。例如,數據流量進來,須要對它進行採樣統計分析,或者作一些更精細化的流量調度,如此衆多的業務需求,若是像前面提到的在linuxkernel內部作優化,作擴展的話,挑戰仍是蠻大的。正是由於咱們有如此多的業務需求,因此咱們選擇了在kernel以外作這樣的事情。

咱們的服務器常常會有一種現象,CPU0 使用率很高,但大部分的CPU核心很空閒,咱們在編程的時候,多進程模型或多線程模型,只是描述程序的併發模型,和系統的多核調度並無太多直接的聯繫。這些調度模型是否可以充分利用多核,就像前面提到的CPU親和性,將某個進程或線程綁定在指定的CPU核心,充分利用CPU核心的緩存,減小在上下文切換等。這裏產生了另一個問題,雖然這種方式是能解決對於CPU多核利用率不佳的問題,但這種硬性關聯沒法解決咱們業務上的需求,也就是說結合業務,咱們更瞭解應該怎樣調度咱們計算資源和數據。

剛剛提到了多進程和多線程,有些同窗說用協程的模型也能夠來解決這個問題吧?但協程只是讓開發者更容易使用的一種封裝,在進程內模擬併發,並自行管理上下文,比系統提供的要輕一些,但最終仍是落在如今的調度模型上。這裏有一個比較有爭議的建議,不見得在任何場景中都適用。

前面已經提到過,對於在一條網絡鏈接上的數據處理,儘量保持在一個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卡帶來的。做爲軟件開發人員不只僅代碼寫的漂亮,質量高,必定要對硬件有必定的瞭解,作運維的同窗不只須要對硬件,對中間件,對服務器瞭解的同時,也同時多看看業務方有怎樣的需求,怎麼樣把資源和業務結合的更好,更好的爲咱們的用戶提供更好的服務。

相關文章
相關標籤/搜索