[導讀] ↓推薦關注↓我們現(xiàn)在已經(jīng)搞定了C10K并發(fā)連接問題,升級一下,如何支持千萬級的并發(fā)連接?你可能說,這不可能。你說錯了,現(xiàn)在的系統(tǒng)可以支持千萬級的并發(fā)連接,只不過所使用的那些激進的技術,并不為人所熟悉。要了解這是如何做到的,我們得求助于ErrataSecurity的CEORober...
我們現(xiàn)在已經(jīng)搞定了 C10K并發(fā)連接問題 ,升級一下,如何支持千萬級的并發(fā)連接?你可能說,這不可能。你說錯了,現(xiàn)在的系統(tǒng)可以支持千萬級的并發(fā)連接,只不過所使用的那些激進的技術,并不為人所熟悉。
要了解這是如何做到的,我們得求助于Errata Security的CEO Robert Graham,看一下他在 Shmoocon 2013 的絕對奇思妙想的演講,題目是 C10M Defending The Internet At Scale。
Robert以一種我以前從來沒有聽說過的才華橫溢的方式來搭建處理這個問題的架構。他的開場是一些歷史,關于Unix最初為什么不是設計成一個通用的服務器的OS,而是為電話網(wǎng)絡的控制系統(tǒng)設計的。真正傳輸數(shù)據(jù)的是電話網(wǎng)絡,因而控制層和數(shù)據(jù)層有非常清晰的區(qū)分。
問題是,我們現(xiàn)在用的Unix服務器還是數(shù)據(jù)層的一部分,雖然并不應當是這樣的。如果一臺服務器只有一個應用程序,為這樣的系統(tǒng)設計內核,與設計一個多用戶系統(tǒng)的內核的區(qū)別是非常大的。
關鍵問題:
不要讓內核去做所有繁重的處理。把數(shù)據(jù)包處理,內存管理以及處理器調度從內核移到可以讓他更高效執(zhí)行的應用程序中去。 讓Linux去處理控制層,數(shù)據(jù)層由應用程序來處理。
結果就是成為一個用200個時鐘周期處理數(shù)據(jù)包,14萬個時鐘周期來處理應用程序邏輯,可以處理1000萬并發(fā)連接的系統(tǒng)。而作為重要的內存訪問花費300個時鐘周期,這是盡可能減少編碼和緩存的設計方法的關鍵。
用一個面向數(shù)據(jù)層的系統(tǒng)你可以每秒處理1000萬個數(shù)據(jù)包。用一個面向控制層的系統(tǒng)每秒你只能獲得1百萬個數(shù)據(jù)包。
這貌似有點極端,你不能局限于操作系的性能,你必須自己去實現(xiàn)。
現(xiàn)在,讓我們學習Robert是怎樣創(chuàng)作一個能處理1000萬并發(fā)連接的系統(tǒng)……
C10K的問題——過去十年
十年前,工程師在處理C10K可擴展性問題時,都盡可能的避免服務器處理超過10,000個的并發(fā)連接。通過修正操作系統(tǒng)內核以及用事件驅動型服務器(如Nginx和Node)替代線程式的服務器(如Apache)這個問題已經(jīng)解決。從Apache轉移到可擴展的服務器上,人們用了十年的時間。在過去的幾年中,(我們看到)可擴展服務器的采用率在大幅增長。
Apache的問題
Apache的問題是,(并發(fā))連接數(shù)越多它的性能會越低下。
關鍵問題 :(服務器的)性能和可擴展性并不是一碼事。它們指的不是同一件事情。當人們談論規(guī)模的時候往往也會談起性能的事情,但是規(guī)模和性能是不可同日而語的。比如Apache。
在僅持續(xù)幾秒的短時連接時,比如快速事務處理,如果每秒要處理1,000個事務,那么大約有1,000個并發(fā)連接到服務器。 如果事務增加到10秒,要保持每秒處理1,000個事務就必須要開啟10K(10,000個)的并發(fā)連接。這時Apache的性能就會陡降,即使拋開DDos攻擊。僅僅是大量的下載就會使Apache宕掉。
如果每秒需要處理的并發(fā)請求從5,000增加到10,000,你會怎么做?假使你把升級硬件把處理器速度提升為原來的兩倍。會是什么情況?你得到了兩倍的性能,但是卻沒有得到兩倍的處理規(guī)模。處理事務的規(guī)?;蛟S僅僅提高到了每秒6,000個(即每秒6,000個并發(fā)請求)。
繼續(xù)提高處理器速度,還是無濟于事。甚至當性能提升到16倍時,并發(fā)連接數(shù)還不能達到10,000個。由此,性能和規(guī)模并不是一回事。
問題在于Apache總是創(chuàng)建了一個進程然后又把它關閉了,這并不是可擴展的。為什么?因為內核采用的O(n^2) 算法導致服務器不能處理10,000個并發(fā)連接。
內核中的兩個基本問題:
連接數(shù) = 線程數(shù)/進程數(shù)。當一個包(數(shù)據(jù)包)來臨時,它(內核)會遍歷所有的10,000個進程以決定由哪個進程處理這個包。
連接數(shù)= 選擇數(shù)/輪詢次數(shù)(單線程情況下)。同樣的擴展性問題。每個包不得不遍歷一遍列表中的socket。
解決方法:修正內核在規(guī)定的時間內進行查找
不管有多少線程,線程切換的時間都是恒定的。
使用一個新的可擴展的epoll()/IOCompletionPort在規(guī)定的時間內做socket查詢。
由于線程調度依然沒有被擴展,因此服務器對socket大規(guī)模的采用epoll,導致需要使用異步編程模式,然而這正是Node和Nginx所采用的方式。這種軟件遷移會得到(和原來)不一樣的表現(xiàn)(指從apache遷移到ngix等)。
即使在一臺很慢(配置較低)的服務器上增加連接數(shù)性能也不會陡降。介于此,在開啟10K并發(fā)連接時,一臺筆記本電腦(運行ngix)的速度甚至超越了一臺16核的服務器(運行Apache)。
異步編程模型可以參考: 深入理解重要的編程模型
C10M問題 —— 下一個十年
在不久的將來,服務器將需要處理數(shù)百萬的并發(fā)連接。由于IPV6普及,連接到每一個服務器的潛在可能連接數(shù)目將達到數(shù)百萬,所以我們需要進入下一個可擴張性階段。
示例應用程序將會用到這類可擴張性方案:IDS/IPS,因為他們是連接到一臺服務器的主干。另一個例子:DNS根服務器、TOR節(jié)點、Nmap互聯(lián)網(wǎng)絡、視頻流、銀行業(yè)務、NAT載體、網(wǎng)絡語音電話業(yè)務PBX、負載均衡器、web緩存、防火墻、郵件接收、垃圾郵件過濾。
通常人們認為互聯(lián)網(wǎng)規(guī)模問題是個人計算機而不是服務器,因為他們銷售的是硬件 軟件。你買的設備連接到你的數(shù)據(jù)中心。這些設備可能包含英特爾主板或網(wǎng)絡處理器和用于加密的芯片、數(shù)據(jù)包檢測,等等。
2013年2月40gpbs、32核、256gigs RAM X86在新蛋的售價為$5000。這種配置的服務器能夠處理10K以上的連接。如果不能,這不是底層的硬件問題,那是因為你選錯了軟件。這樣的硬件能夠輕而易舉的支持千萬的并發(fā)連接。
10,000,000個并發(fā)連接挑戰(zhàn)意味著什么
1. 10,000,000個并發(fā)連接
2. 每秒1,000,000個連接——每個連接大約持續(xù)10秒
3. 10千兆比特/每秒——快速連接到互聯(lián)網(wǎng)。
4. 10,000,000包/每秒——預期當前服務器處理50,000包/每秒,這將導致更高的級別。服務器能夠用來處理每秒100,000個中斷和每個包引發(fā)的中斷。
5. 10微秒延遲——可擴張的服務器也許能夠處理這樣的增長,但是延遲將會很突出。
6. 10微秒上下跳動——限制最大延遲
7. 10個一致的CPU內核——軟件應該擴張到更多內核。典型的軟件只是簡單的擴張到四個內核。服務器能夠擴張到更多的內核,所以軟件需要被重寫以支持在擁有更多內核的機器上運行。
我們學的是Unix而不是網(wǎng)絡編程(Network Programming)
一代代的程序員通過W. Richard Stevens所著的《Unix網(wǎng)絡編程》(Unix Networking Programming)學習網(wǎng)絡編程技術。問題是,這本書是關于Unix的,并不是網(wǎng)絡編程。它講述的是,你僅需要寫一個很小的輕量級的服務器就可以讓Unix做一切繁復的工作。然而內核并不是規(guī)模的(規(guī)模不足)。解決方案是,將這些繁復的工作轉移到內核之外,自已處理。
一個頗具影響的例子,就是在考慮到Apache的線程每個連接模型(is to consider Apache’s thread per connection model)。 這就意味著線程調度器根據(jù)到來的數(shù)據(jù)(on which data arrives)決定調用哪一個(不同的)read()函數(shù)(方法)。把線程調度系統(tǒng)當做(數(shù)據(jù))包調度系統(tǒng)來使用(我非常喜歡這一點,之前從來沒聽說過類似的觀點)。
Nginx宣稱,它并不把線程調度當作(數(shù)據(jù))包調度來用使用,它自已做(進行)包調度。使用select來查找socket,我們知道數(shù)據(jù)來了,于是就可以立即讀取并處理它,數(shù)據(jù)也不會堵塞。
經(jīng)驗:讓Unix/Linux處理網(wǎng)絡堆棧,之后的事情就由你自已來處理。
你怎么編寫軟件使其可伸縮?
你怎么改變你的軟件使其可伸縮?有大量的經(jīng)驗規(guī)則都是假設硬件能處理多少。我們需要真實的執(zhí)行性能。
要進入下一個等級,我們需要解決的問題是:
包的可擴展性
多核的可擴展性
內存的可擴展性
精簡包-編寫自己的定制驅動來繞過內核堆棧
數(shù)據(jù)包的存在的問題是它們要通過Unix的內核。 網(wǎng)絡堆棧復雜又慢。你的應用程序需要的數(shù)據(jù)包的路徑要更加直接。不要讓操作系統(tǒng)來處理數(shù)據(jù)包。
做到這一點的方法是編寫自己的驅動程序。 所有驅動程序要做到是發(fā)送數(shù)據(jù)包到你的應用程序而不是通過內核協(xié)議棧。你可以找得到驅動有: PF_RING,Netmap,Interl DPDK(數(shù)據(jù)層開發(fā)套件)。
有多快呢?Inter有一個基準是在一個輕量級的服務器上每秒可以處理8000萬的數(shù)據(jù)包(每個數(shù)據(jù)包200個時鐘周期)。這也是通過用戶模式。數(shù)據(jù)包通過用戶模式后再向下傳遞。當Linux獲得UDP數(shù)據(jù)包后通過用戶模式在向下傳遞時,它每秒處理的數(shù)據(jù)包不會超過100萬個??蛻趄寗訉inux來說性能比是80:1。
如果用200個時間周期來每秒獲得1000萬個數(shù)據(jù)包,那么可以剩下1400個時鐘周期來實現(xiàn)一個類似DNS/IDS的功能。
用PF_RING/DPDK來獲得原始的數(shù)據(jù)包的話,你必須自己去做TCP協(xié)議棧。 人們正在做用戶模式的堆棧。對于Inter來講已有一個提供真正可擴展性能的可用的TCP堆棧。
多核的可擴展性
多核的可擴展性和多線程可擴展性是不一樣的。我們熟知的idea處理器不在漸漸變快,但是我們卻擁有越來越多的idea處理器。
大多數(shù)代碼并不能擴展到4核。當我們添加更多的核心時并不是性能不變,而是我們添加更多的核心時越來越慢。因為我們編寫的代碼不好。我們期望軟件和核心成線性的關系。我們想要的是添加更多的核心就更快。
多線程編程不是多核編程
多線程:
每個CPU有多個線程
鎖來協(xié)調線程(通過系統(tǒng)調用)
每個線程有不同的任務
多核:
每個CPU核心一個線程
當兩個核心中的兩個不同線程訪問同一數(shù)據(jù)時,它們不用停止來相互等待
所有線程是同一任務的一部分
我們的問題是如何讓一個程序能擴展到多個核心。Unix中的鎖是在內核中實現(xiàn)的,在4核心上使用鎖會發(fā)生什么? 大多數(shù)軟件會等待其他線程釋放一個鎖,這樣的以來你有更多的CPU核心內核就會耗掉更多的性能。
我們需要的是一個像高速公路的架構而不是一個像靠紅綠燈控制的十字路口的架構。我們想用盡可能少的小的開銷來讓每個人在自己的節(jié)奏上而沒有等待。
解決方案:
保持每一個核心的數(shù)據(jù)結構,然后聚集起來讀取所有的組件。
原子性. CPU支持的指令集可以被C調用。保證原子性且沒有沖突是非常昂貴的,所以不要期望所有的事情都使用指令。
無鎖的數(shù)據(jù)結構。線程間訪問不用相互等待。不要自己來做,在不同架構上來實現(xiàn)這個是一個非常復雜的工作。
線程模型。線性線程模型與輔助線程模型。問題不僅僅是同步。而是怎么架構你的線程。
處理器族。告訴操作系統(tǒng)使用前兩個核心。之后設置你的線程運行在那個核心上。你也可以使用中斷來做同樣的事兒。所以你有多核心的CPU,但這不關Linux事。
內存的可擴展性
假設你有20G內存(RAM),第個連接占用2K,假如你只有20M三級緩存(L3 cache),緩存中沒有數(shù)據(jù)。從緩存轉移到主存上消耗300個時鐘周期,此時CPU處于空閑狀態(tài)。想象一下,(處理)每個包要1400個時鐘周期。
切記還有200時鐘周期/每包的開銷(應該指等待包的開銷)。每個包有4次高速緩存的缺失,這是個問題。
提高緩存效率,不要使用指針在整個內存中隨便亂放數(shù)據(jù)。每次你跟蹤一個指針都會造成一次高速緩存缺失:[hash pointer] -> [Task Control Block] -> [Socket] -> [App]。這造成了4次高速緩存缺失。 將所有的數(shù)據(jù)保持在一個內存塊中:[TCB | Socket | App]. 為每個內存塊預分配內存。這樣會將高速緩存缺失從4降低到1。
分頁,32G的數(shù)據(jù)需要占用64M的分頁表,不適合都放在高速緩存上。所以造成2個高速緩存缺失,一個是分頁表另一個是它指向的數(shù)據(jù)。這些細節(jié)在開發(fā)可擴展軟件時是不可忽略的。 解決:壓縮數(shù)據(jù),使用有很多內存訪問的高速架構,而不是二叉搜索樹。NUMA加倍了主內存的訪問時間。內存有可能不在本地,而在其它地方
內存池 , 在啟動時立即分配所有的內存。在對象(object)、線程(thread)和socket的基礎上分配(內存)。
超線程,提高CPU使用率,減少延遲,比如當在內存訪問中一個線程等待另一個全速線程,這種情況,超線程CPU可以并行執(zhí)行,不用等待。
大內存頁, 減小頁表的大小。從一開始就預留內存,并且讓應用程序管理內存。
選擇合適的語言
go語言這種天生為并發(fā)而生的語言,完美的發(fā)揮了服務器多核優(yōu)勢,很多可以并發(fā)處理的任務都可以使用并發(fā)來解決,比如go處理http請求時每個請求都會在一個goroutine中執(zhí)行,C和C 語言當然也可以實現(xiàn)高并發(fā)系統(tǒng), 總之:怎樣合理的壓榨CPU,讓其發(fā)揮出應有的價值,是優(yōu)化一直需要探索學習的方向。
推薦開源項目學習:
F-Stack 開發(fā)框架 F-Stack 是一款兼顧高性能、易用性和通用性的網(wǎng)絡開發(fā)框架,傳統(tǒng)上 DPDK 大多用于 SDN、NFV、DNS 等簡單的應用場景下,對于復雜的 TCP 協(xié)議棧上的七層應用很少,市面上已出現(xiàn)了部分用戶態(tài)協(xié)議棧,如 mTCP、Mirage、lwIP、NUSE 等,也有用戶態(tài)的編程框架,如 SeaStar 等,但統(tǒng)一的特點是應用程序接入門檻較高,不易于使用。
F-Stack 使用純 C 實現(xiàn),充當膠水粘合了 DPDK、FreeBSD 用戶態(tài)協(xié)議棧、Posix API、微線程框架和上層應用(Nginx、Redis),使絕大部分的網(wǎng)絡應用可以通過直接修改配置或替換系統(tǒng)的網(wǎng)絡接口即可接入 F-Stack,從而獲得更高的網(wǎng)絡性能。
F-Stack 架構
F-Stack 總體架構如上圖所示,具有以下特點:
使用多進程無共享架構。
各進程綁定獨立的網(wǎng)卡隊列和 CPU,請求通過設置網(wǎng)卡 RSS 散落到各進程進行處理。
各進程擁有獨立的協(xié)議棧、PCB 表等資源。
每個 NUMA 節(jié)點使用獨立的內存池。
進程間通信通過無鎖環(huán)形隊列(rte_ring)進行。
使用 DPDK 作為網(wǎng)絡 I/O 模塊,將數(shù)據(jù)包從網(wǎng)卡直接接收到用戶態(tài)。
移植 FreeBSD Release 11.0.1 協(xié)議棧到用戶態(tài)并與 DPDK 對接。
http://www.f-stack.org
Nginx :
一個高性能的HTTP和反向代理web服務器,同時也提供了IMAP/POP3/SMTP服務。
http://nginx.org/
Redis :
一個開源的使用ANSI C語言編寫、遵守BSD協(xié)議、支持網(wǎng)絡、可基于內存亦可持久化的日志型。
https://redis.io/
Fasthttp :
Fasthttp是一個高性能的web server框架。Golang官方的net/http性能相比fasthttp遜色很多。根據(jù)測試,fasthttp的性能可以達到net/http的10倍。所以,在一些高并發(fā)的項目中,我們經(jīng)常用fasthttp來代替net/http。
https://github.com/savsgio/atreugo
Oat :
oat 是一個輕量級高性能 Web 服務開發(fā)框架,采用純 C 編寫而成。
https://gitee.com/mirrors/oatpp
Undertow,jetty,Tomcat等:
java語言的高并發(fā)框架。
https://undertow.io/
https://gitee.com/mirrors/jetty
https://tomcat.apache.org/
性能測試對比(排行榜):
https://www.techempower.com/benchmarks/
有興趣同學可以試一試你的極限優(yōu)化,讓你們的程序上榜!這個絕對是簡歷和項目推薦里面的最有力的說明。
參考
翻譯:DYOS, 裴寶亮, dexterman, zicode
https://www.oschina.net/translate/the-secret- to-10-million-concurrent-connections-the-kernel?lang=chs
- EOF -
本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。