千萬并發的秘密-內核是問題的根本

jopen 11年前發布 | 31K 次閱讀 并發

我們現在已經搞定了 C10K并發連接問題 ,升級一下,如何支持千萬級的并發連接?你可能說,這不可能。你說錯了,現在的系統可以支持千萬級的并發連接,只不過所使用的那些激進的技術,并不為人所熟悉。

要了解這是如何做到的,我們得求助于Errata Security的CEO Robert Graham,看一下他在 Shmoocon 2013 的絕對奇思妙想的演講,題目是 C10M Defending The Internet At Scale

Robert以一種我以前從來沒有聽說過的才華橫溢的方式來搭建處理這個問題的架構。他的開場是一些歷史,關于Unix最初為什么不是設計成一個通用的服務器的OS,而是為電話網絡的控制系統設計的。真正傳輸數據的是電話網絡,因而控制層和數據層有非常清晰的區分。問題是,我們現在用的Unix服務器還是數據層的一部分,雖然并不應當是這樣的。如果一臺服務器只有一個應用程序,為這樣的系統設計內核,與設計一個多用戶系統的內核的區別是非常大的。

這也是為什么他說重要的是要理解:

  • 解決方案不是內核,而是問題所在。

這意味著:

  • 不要讓內核去做所有繁重的調度。把數據包處理,內存管理以及處理器調度從內核移到可以讓他更高效執行的應用程序中去。讓Linux去處理控制層,數據層由應用程序來處理。

結果就是成為一個用200個時鐘周期處理數據包,14萬個時鐘周期來處理應用程序邏輯,可以處理1000萬并發連接的系統。而作為重要的內存訪問花費300個時鐘周期,這是盡可能減少編碼和緩存的設計方法的關鍵。

用一個面向數據層的系統你可以每秒處理1000萬個數據包。用一個面向控制層的系統每秒你只能獲得1百萬個數據包。

如果這貌似有點極端,記住一句老話:可擴展性是專業化。要做些牛X的事兒,你不能局限于操作系的性能。你必須自己去做。

現在,讓我們學習Robert是怎樣創作一個能處理1000萬并發連接的系統……

C10K的問題——過去十年 

十年前,工程師在處理C10K可擴展性問題時,都盡可能的避免服務器處理超過10,000個的并發連接。通過修正操作系統內核以及用事件驅動型服務器(如 Nginx和Node)替代線程式的服務器(如Apache)這個問題已經解決。從Apache轉移到可擴展的服務器上,人們用了十年的時間。在過去的幾年中,(我們看到)可擴展服務器的采用率在大幅增長。

Apache的問題

  • Apache的問題是,(并發)連接數越多它的性能會越低下。
  • 關鍵問題:(服務器的)性能和可擴展性并不是一碼事。它們指的不是同一件事情。當人們談論規模的時候往往也會談起性能的事情,但是規模和性能是不可同日而語的。比如Apache。
  • 在僅持續幾秒的短時連接時,比如快速事務處理,如果每秒要處理1000個事務,那么大約有1000個并發連接到服務器。
  • 如果事務增加到10秒,要保持每秒處理1000個事務就必須要開啟10K(10000個)的并發連接。這時Apache的性能就會陡降,即使拋開DDos攻擊。僅僅是大量的下載就會使Apache宕掉。
  • 如果每秒需要處理的并發請求從5,000增加到10,000,你會怎么做?假使你把升級硬件把處理器速度提升為原來的兩倍。會是什么情況?你得到了兩倍的性能,但是卻沒有得到兩倍的處理規模。處理事務的規模或許僅僅提高到了每秒6,000個(即每秒6000個并發請求)。繼續提高處理器速度,還是無濟于事。甚至當性能提升到16倍時,并發連接數還不能達到1000個。由此,性能和規模并不是一回事。
  • 問題在于Apache總是創建了一個進程然后又把它關閉了。這并不是規模的。
  • 為什么?因為內核采用的O(n^2) 算法導致服務器不能處理10K個并發連接。
    • 內核中的兩個基本問題:
      • 連接數 = 線程數/進程數。當一個包(數據包)來臨時,它(內核)會遍歷所有的10K進程以決定由哪個進程處理這個包。
      • 連接數= 選擇數/輪詢次數(單線程情況下)。同樣的擴展性問題。每個包不得不遍歷一遍列表中的socket。
    • 解決方法:修正內核在規定的時間內進行查找
      • 不管有多少線程,線程切換的時間都是恒定的
      • 使用一個新的可擴展的epoll()/IOCompletionPort在規定的時間內做socket查詢
  • 由于線程調度依然沒有被擴展,因此服務器對sockt大規模的采用epoll,導致需要使用異步編程模式,然而這正是Node和Nginx所采用的方式。這種軟件遷移會得到(和原來)不一樣的表現(指從apache遷移到ngix等)。即使在一臺很慢(配置較低)的服務器上增加連接數性能也不會陡降。介于此,在開啟10K并發連接時,一臺筆記本電腦(運行ngix)的速度甚至超越了一臺16核的服務器(運行Apache)。

C10M問題 —— 下一個十年

在不久的將來,服務器將需要處理數百萬的并發連接。由于IPV6普及,連接到每一個服務器的潛在可能連接數目將達到數百萬,所以我們需要進入下一個可擴張性階段。

示例應用程序將會用到這類可擴張性方案:IDS/IPS,因為他們是連接到一臺服務器的主干。另一個例子:DNS根服務器、TOR節點、Nmap互聯網絡、視頻流、銀行業務、NAT載體、網絡語音電話業務PBX、負載均衡器、web緩存、防火墻、郵件接收、垃圾郵件過濾。

通常人們認為互聯網規模問題是個人計算機而不是服務器,因為他們銷售的是硬件+軟件。你買的設備連接到你的數據中心。這些設備可能包含英特爾主板或網絡處理器和用于加密的芯片、數據包檢測,等等。

2013年2月40gpbs、32核、256gigs RAM X86在新蛋的售價為$5000。這種配置的服務器能夠處理10K以上的連接。如果不能,這不是底層的硬件問題,那是因為你選錯了軟件。這樣的硬件能夠輕而易舉的支持千萬的并發連接。

10,000,000個并發連接挑戰意味著什么

1. 10,000,000個并發連接

2. 每秒1,000,000個連接——每個連接大約持續10秒

3. 10千兆比特/每秒——快速連接到互聯網。

4. 10,000,000包/每秒——預期當前服務器處理50,000包/每秒,這將導致更高的級別。服務器能夠用來處理每秒100,000個中斷和每個包引發的中斷。

5. 10微秒延遲——可擴張的服務器也許能夠處理這樣的增長,但是延遲將會很突出。

6. 10微秒上下跳動——限制最大延遲

7. 10個一致的CPU內核——軟件應該擴張到更多內核。典型的軟件只是簡單的擴張到四個內核。服務器能夠擴張到更多的內核,所以軟件需要被重寫以支持在擁有更多內核的機器上運行。

我們學的是Unix而不是網絡編程(Network Programming) 

  • 一代代的程序員通過W. Richard Stevens所著的《Unix網絡編程》(Unix Networking Programming學習網絡編程技術。問題是,這本書是關于Unix的,并不是網絡編程。它講述的是,你僅需要寫一個很小的輕量級的服務器就可以讓Unix做一切繁復的工作。然而內核并不是規模的(規模不足)。解決方案是,將這些繁復的工作轉移到內核之外,自已處理。
  • 一個頗具影響的例子,就是在考慮到Apache的線程每個連接模型(is to consider Apache’s thread per connection model)。 這就意味著線程調度器根據到來的數據on which data arrives)決定調用哪一個(不同的)read()函數(方法)。把線程調度系統當做(數據)包調度系統來使用(我非常喜歡這一點,之前從來沒聽說過類似的觀點)。
  • Nginx宣稱,它并不把線程調度當作(數據)包調度來用使用,它自已做(進行)包調度。使用select來查找socket,我們知道數據來了,于是就可以立即讀取并處理它,數據也不會堵塞。
  • 經驗:讓Unix處理網絡堆棧,之后的事情就由你自已來處理。

你怎么編寫軟件使其可伸縮

你怎么改變你的軟件使其可伸縮?有大量的經驗規則都是假設硬件能處理多少。我們需要真實的執行性能。

要進入下一個等級,我們需要解決的問題是:

  1. 包的可擴展性
  2. 多核的可擴展性
  3. 內存的可擴展性

精簡包-編寫自己的定制驅動來繞過堆棧

  • 數據包的存在的問題是它們要通過Unix的內核。 網絡堆棧復雜又慢。你的應用程序需要的數據包的路徑要更加直接。不要讓操作系統來處理數據包。
  • 做到這一點的方法是編寫自己的驅動程序。所有驅動程序要做到是發送數據包到你的應用程序而不是通過堆棧。你可以找得到驅動有:PF_RING,Netmap,Interl DPDK(數據層開發套件),英特爾是閉源的,但是有許多繞開它的支持。
  • 有多快呢?Inter有一個基準是在一個輕量級的服務器上每秒可以處理8000萬的數據包(每個數據包200個時鐘周期)。這也是通過用戶模式。數據包通過用戶模式后再向下傳遞。當Linux獲得UDP數據包后通過用戶模式在向下傳遞時,它每秒處理的數據包不會超過100萬個。客戶驅動對Linux來說性能比是80:1。
  • 如果用200個時間周期來每秒獲得1000萬個數據包,那么可以剩下1400個時鐘周期來實現一個類似DNS/IDS的功能。
  • 用PF_RING來獲得原始的數據包的話,你必須自己去做TCP協議棧。人們正在做用戶模式的堆棧。對于Inter來講已有一個提供真正可擴展性能的可用的TCP堆棧。 

多核的可擴展性

多核的可擴展性和多線程可擴展性是不一樣的。 我們熟知的idea處理器不在漸漸變快,但是我們卻擁有越來越多的idea處理器。

大多數代碼并不能擴展到4核。當我們添加更多的核心時并不是性能不變,而是我們添加更多的核心時越來越慢。因為我們編寫的代碼不好。我們期望軟件和核心成線性的關系。我們想要的是添加更多的核心就更快。

多線程編程不是多核編程

  • 多線程:
    每個CPU有多個線程


    • 鎖來協調線程(通過系統調用)

    • 每個線程有不同的任務

  • 多核:

    • 每個CPU核心一個線程
    • 當兩個核心中的兩個不同線程訪問同一數據時,它們不用停止來相互等待
    • 所有線程是同一任務的一部分
  • 我們的問題是如何讓一個程序能擴展到多個核心。
  • Unix中的鎖是在內核中實現的。在4核心上使用鎖會發生什么?大多數軟件會等待其他線程釋放一個鎖。這樣的以來你有更多的CPU核心內核就會耗掉更多的性能。
  • 我們需要的是一個像高速公路的架構而不是一個像靠紅綠燈控制的十字路口的架構。我們想用盡可能少的小的開銷來讓每個人在自己的節奏上而沒有等待。
  • 解決方案:
    • 保持每一個核心的數據結構,然后聚集起來讀取所有的組件。
    • 原子性. CPU支持的指令集可以被C調用。 保證原子性且沒有沖突是非常昂貴的,所以不要期望所有的事情都使用指令。
    • 無鎖的數據結構。線程間訪問不用相互等待。不要自己來做,在不同架構上來實現這個是一個非常復雜的工作。
    • 線程模型。線性線程模型與輔助線程模型。問題不僅僅是同步。而是怎么架構你的線程。
    • 處理器族。告訴操作系統使用前兩個核心。之后設置你的線程運行在那個核心上。你也可以使用中斷來做同樣的事兒。所以你有多核心的CUP,但這不關Linux啥吊事兒。

內存的可擴展性

  • 問題:假設你有20G內存(RAM),第個連接占用2K,假如你只有20M三級緩存(L3 cache),緩存中沒有數據。從緩存轉移到主存上消耗300個時鐘周期,此時CPU處于空閑狀態。
  • 想象一下,(處理)每個包要1400個時鐘周期。切記還有200時鐘周期/每包的開銷(應該指等待包的開銷)。每個包有4次高速緩存的缺失,這是個問題。
  • 協同定位數據
    • 不要使用指針在整個內存中隨便亂放數據。每次你跟蹤一個指針都會造成一次高速緩存缺失:[hash pointer] -> [Task Control Block] -> [Socket] -> [App]。這造成了4次高速緩存缺失。
    • 將所有的數據保持在一個內存塊中:[TCB | Socket | App]. 為每個內存塊預分配內存。這樣會將高速緩存缺失從4降低到1。
  • 分頁
    • 32G的數據需要占用64M的分頁表,不適合都放在高速緩存上。所以造成2個高速緩存缺失,一個是分頁表另一個是它指向的數據。這些細節在開發可擴展軟件時是不可忽略的。
    • 解決:壓縮數據,使用有很多內存訪問的高速架構,而不是二叉搜索樹。
    • NUMA加倍了主內存的訪問時間。內存有可能不在本地,而在其它地方。
  • 內存池
    • 在啟動時立即分配所有的內存。
    • 在對象(object)、線程(thread)和socket的基礎上分配(內存)。
  • 超線程
    • (一個)網絡處理器能運行4個線程,Intel只能運行2個。
    • 掩蓋延遲,比如,當在內存訪問中一個線程等待另一個全速線程。
  • 大內存頁
    • 減小頁表的大小。從一開始就預留內存,并且讓應用程序管理(內存)。

總結 

  • 網卡(NIC,Network Interface Card
    • 問題:通過內核驅動并不完美。
    • 解決:使用你自已的驅動程序管理它(網卡),使適配器(網卡)遠離操作系統(內核)。
  • CPU
    • 問題:采用傳統的內核方法(即使用內核驅動)來協調應用程序,效果并不是很好。
    • 解決:讓Linux管理前兩個CPU,您的應用程序管理其余的CPU。這樣中斷就不會發生在不允許的CPU上。
  • 內存
    • 問題:為了使其更好的工作,(內存)需要特別的關照。
    • 解決:在系統啟動時就給你管理的大頁面(hugepages )分配大多數的內存.

(僅)把控制層留給Linux,與數據層毫無瓜葛。由應用程序管理數據層。應用程序與內核間沒有交互。沒有線程調度,沒有系統調用,沒有中斷,什么都沒有。

然而,你擁有的是在Linux上運行的代碼,并且可以正常調試,它并不是某些需要特殊定制的怪異的硬件系統。你得到了定制硬件的性能,就像你期待的那樣,只是需要用你熟悉的編程(語言)和開發環境。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!