深入理解高併發服務器性能優化

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

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

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

關鍵問題:

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

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

這貌似有點極端,你不能侷限於操作系的性能,你必須自己去實現。

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

C10K 的問題——過去十年 

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

Apache 的問題

Apache 的問題是,(併發)連接數越多它的性能會越低下。

關鍵問題:(服務器的)性能和可擴展性並不是一碼事。它們指的不是同一件事情。當人們談論規模的時候往往也會談起性能的事情,但是規模和性能是不可同日而語的。比如 Apache。

在僅持續幾秒的短時連接時,比如快速事務處理,如果每秒要處理 1,000 個事務,那麼大約有 1,000 個併發連接到服務器。如果事務增加到 10 秒,要保持每秒處理 1,000 個事務就必須要開啓 10K(10,000 個)的併發連接。這時 Apache 的性能就會陡降,即使拋開 DDos 攻擊。僅僅是大量的下載就會使 Apache 宕掉。

如果每秒需要處理的併發請求從 5,000 增加到 10,000,你會怎麼做? 假使你把升級硬件把處理器速度提升爲原來的兩倍。會是什麼情況? 你得到了兩倍的性能,但是卻沒有得到兩倍的處理規模。處理事務的規模或許僅僅提高到了每秒 6,000 個(即每秒 6,000 個併發請求)。繼續提高處理器速度,還是無濟於事。甚至當性能提升到 16 倍時,併發連接數還不能達到 10,000 個。由此,性能和規模並不是一回事。

問題在於 Apache 總是創建了一個進程然後又把它關閉了,這並不是可擴展的。爲什麼? 因爲內核採用的 O(n^2) 算法導致服務器不能處理 10,000 個併發連接。

由於線程調度依然沒有被擴展,因此服務器對 socket 大規模的採用 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) 

你怎麼編寫軟件使其可伸縮?

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

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

  1. 包的可擴展性

  2. 多核的可擴展性

  3. 內存的可擴展性                 

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

多核的可擴展性

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

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

多線程編程不是多核編程

多線程:

多核:

我們的問題是如何讓一個程序能擴展到多個核心。Unix 中的鎖是在內核中實現的,在 4 核心上使用鎖會發生什麼? 大多數軟件會等待其他線程釋放一個鎖,這樣的以來你有更多的 CPU 核心內核就會耗掉更多的性能。

我們需要的是一個像高速公路的架構而不是一個像靠紅綠燈控制的十字路口的架構。我們想用盡可能少的小的開銷來讓每個人在自己的節奏上而沒有等待。

解決方案:  

內存的可擴展性

假設你有 20G 內存(RAM),第個連接佔用 2K,假如你只有 20M 三級緩存(L3 cache),緩存中沒有數據。從緩存轉移到主存上消耗 300 個時鐘週期,此時 CPU 處於空閒狀態。想象一下,(處理)每個包要 1400 個時鐘週期。切記還有 200 時鐘週期 / 每包的開銷(應該指等待包的開銷)。每個包有 4 次高速緩存的缺失,這是個問題。

選擇合適的語言

go 語言這種天生爲併發而生的語言,完美的發揮了服務器多核優勢,很多可以併發處理的任務都可以使用併發來解決,比如 go 處理 http 請求時每個請求都會在一個 goroutine 中執行,C 和 C++ 語言當然也可以實現高併發系統,總之: 怎樣合理的壓榨 CPU, 讓其發揮出應有的價值,是優化一直需要探索學習的方向。

推薦開源項目學習:

F-Stack 開發框架

F-Stack 是一款兼顧高性能、易用性和通用性的網絡開發框架,傳統上 DPDK 大多用於 SDN、NFV、DNS 等簡單的應用場景下,對於複雜的 TCP 協議棧上的七層應用很少,市面上已出現了部分用戶態協議棧,如 mTCP、Mirage、lwIP、NUSE 等,也有用戶態的編程框架,如 SeaStar 等,但統一的特點是應用程序接入門檻較高,不易於使用。

F-Stack 使用純 C 實現,充當膠水粘合了 DPDK、FreeBSD 用戶態協議棧、Posix API、微線程框架和上層應用(Nginx、Redis),使絕大部分的網絡應用可以通過直接修改配置或替換系統的網絡接口即可接入 F-Stack,從而獲得更高的網絡性能。

F-Stack 架構

F-Stack 總體架構如上圖所示,具有以下特點:

http://www.f-stack.org

Nginx :

一個高性能的 HTTP 和反向代理 web 服務器,同時也提供了 IMAP/POP3/SMTP 服務。

http://nginx.org/

**Redis : **

一個開源的使用 ANSI C 語言編寫、遵守 BSD 協議、支持網絡、可基於內存亦可持久化的日誌型。

https://redis.io/

Fasthttp :

Fasthttp 是一個高性能的 web server 框架。Golang 官方的 net/http 性能相比 fasthttp 遜色很多。根據測試,fasthttp 的性能可以達到 net/http 的 10 倍。所以,在一些高併發的項目中,我們經常用 fasthttp 來代替 net/http。

https://github.com/savsgio/atreugo

Oat++:

oat++ 是一個輕量級高性能 Web 服務開發框架,採用純 C++ 編寫而成。

https://gitee.com/mirrors/oatpp

Undertow,jetty,Tomcat 等:

java 語言的高併發框架。

https://undertow.io/

https://gitee.com/mirrors/jetty

https://tomcat.apache.org/

性能測試對比(排行榜):

https://www.techempower.com/benchmarks/

有興趣同學可以試一試你的極限優化,讓你們的程序上榜!這個絕對是簡歷和項目推薦裏面的最有力的說明。

參考

翻譯:DYOS, 裴寶亮, dexterman, zicode

https://www.oschina.net/translate/the-secret-to-10-million-concurrent-connections-the-kernel?lang=chs


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/yEDVk_1tezBzgOvReqXFjg