用 Python 實現每秒處理 120 萬次 HTTP 請求

用 Python 作到每秒處理上百萬次 HTTP 請求,可能嗎?也許不能,但直到最近,這已成爲現實。java

不少公司都在爲了提高程序的執行性能和下降服務器的運營成本,而放棄 Python 去選擇其它編程語言,其實這樣作並非必須,由於 Python 徹底能夠勝任這些任務。python

Python 社區最近作了大量關於性能的優化。CPython 3.6 重寫了新的字典從而全面提高解析器的執行性能。因爲引入更快的調用規則和字典查詢緩存,CPython 3.7 甚至還要更快。git

咱們能夠用 PyPy 的 Just-in-Time 來編譯複雜的科學計算任務,NumPy 的測試套件也優化了和 C 擴展的兼容性,同時 PyPy 還計劃於今年晚些時候作到和 Python 3.5 保持一致。github

瞭解 Japronto!

Japronto 是一個全新的,爲微服務量身打造的微框架。實現它的主要目標包含夠快、可擴展和輕量化。的確它快的嚇人,甚至遠比 NodeJS 和 Go 還要快的多的多。要感謝 asyncio,讓我能夠同時編寫同步和異步代碼。編程

用 Python 實現每秒處理 120 萬次 HTTP 請求

Python 的微框架(藍色)、NodeJS 和 Go (綠色) 和 Japronto (紫色)緩存

勘誤表:用戶 @heppu 提到,若是謹慎點用 Go 的 stdlib HTTP 服務器能夠寫出比上圖的 Go 快 12% 的代碼。另外 fasthttp 也是一個很是棒的 Go 服務器,一樣的測試中它的性能幾乎只比 Japronto 低 18%。真是太棒了!更多細節查能夠看 
https://github.com/squeaky-pl/japronto/pull/12 和 
https://github.com/squeaky-pl/japronto/pull/14服務器

用 Python 實現每秒處理 120 萬次 HTTP 請求

咱們能夠看到其實 Meinheld WSGI 服務器已經和 NodeJS 和 Go 的性能差很少了。儘管它用的是阻塞式設計,但仍是要比前面那四個要快的多,前面四個用的是異步的 Python 解決方案。因此,不要輕易相信別人那些關於異步系統老是比同步系統更快的說法,雖然都是併發處理的問題,但事實遠不如想象的那麼簡單。併發

雖然我只是用 「Hello World」 來完成上面這個關於微框架的測試,但它清晰的展示了各類服務器框架的處理能力。框架

這些測試是在一臺亞馬遜 AWS EC2 的 c4.2xlarge 實例上完成的,它有 8 VCPUs,數據中心選在聖保羅區域,共享主機、HVM 虛擬化、普通磁盤。操做系統是 Ubuntu 16.04.1 LTS (Xenial Xerus),內核爲 Linux 4.4.0–53-generic x86_64。操做系統顯示的 CPU 是 Xeon® E5–2666 v3 @ 2.90GHz。Python 我用的版本是 3.6,剛從源碼編譯來的。異步

公平起見,全部程序,包括 Go,都只運行在單個處理器內核上。測試工具爲 wrk,參數是 1 個線程,100 個連接和每一個連接 24 個請求(累計併發 2400 次請求)。

用 Python 實現每秒處理 120 萬次 HTTP 請求

HTTP 流水線(圖片來自 Wikipedia)

HTTP 流水線在這裏起着決定性的因素,由於 Japronto 用它來作執行併發請求的優化。

大多數服務器把來自客戶端的流水線和非流水線請求都一視同仁,用一樣的方法處理,並無作針對性的優化。(實際上 Sanic 和 Meinheld 也是默默的把流水線請求當作非流水線來處理,這違反了 HTTP 1.1 協議)

簡單來講,經過流水線技術,客戶端不用等到服務器端返回,就能夠在同一條 TCP 連接上繼續發送後續的請求。爲了保障通信的完整性,服務器端會按照請求的順序逐個把結果返回給客戶端。

細節優化過程

當一堆小的 GET 請求被客戶端以流水線打包發送過來,服務器端極可能只須要一次系統調用,讀取一個 TCP 數據包就能拿到所有的請求。

系統調用,以及在內核空間到用戶空間之間移動數據,相比起在進程內部移動數據,成本要高的多。這就是爲何不到萬不得已,要儘量少作系統調用的次數。

當 Japronto 收到數據併成功解析出請求序列時,它會嘗試儘量快的把這些請求執行完成,並以正確的順序合併全部結果,而後只執行一次系統調用發送數據給客戶端。實際上由於有 scatter/gather IO 這樣的系統調用,合併的工做並不須要本身去完成,只不過 Japronto 暫時尚未用到這些功能。

然而事情並不老是那麼完美,有時候請求須要耗費很長時間去處理,等待完成的過程增長了沒必要要的延遲。

當咱們作優化時,有必要考慮系統調用的成本和請求的預期完成時間。

用 Python 實現每秒處理 120 萬次 HTTP 請求

通過優化 Japronto 拿到了 1,214,440 RPS 的成績

除了利用客戶端流水線請求,和優化調用,還有一些其它可用的技術。

Japronto 幾乎都是用 C 寫的。包含解析器、協議、連接管理、路由、請求、應答等對象都是用 C 擴展寫的。

Japronto 力圖作到 Python 的懶加載,好比,協議頭的字典只有在被試圖請求到時纔會被建立,另一系列的對象也只有在第一次使用時纔會被建立。

Japronto 使用超牛逼的 picohttpparser C 庫來解析狀態、協議頭以及分片的 HTTP 消息體。Picohttpparser 是直接調用現代 CPU 集成的 SSE4.2 擴展文本處理指令去快速匹配 HTTP 標記的邊界(那些 10 年前的老 x86_64 CPU 都有這玩意兒)。I/O 用到了超棒的 uvloop,它是一個 libuv 的封裝,在最底層,它是調用 epoll 來提供異步讀寫通知。

用 Python 實現每秒處理 120 萬次 HTTP 請求

Picohttpparser 依賴 SSE4.2 和 CMPESTRI x86_64 的特性作解析

Python 是有垃圾收集功能的語言,爲避免沒必要要的增長垃圾收集器的壓力,在設計高性能系統時必定要多加註意。Japronto 的內部被設計的嘗試避免循環引用和儘量少的分配、釋放內存,它會預先申請一塊區域來存放對象各類,同時嘗試在後續請求中重用那些沒有被繼續引用的 Python 的對象,而不是將那些對象直接扔掉。

這些預先申請的內存的大小被固定爲 4KB 的倍數。內部結構會很是當心和頻繁的使用這些連續的內存區域,以減小緩存失效的可能性。

Japronto 會盡量避免沒必要要的緩存間複製,只在正確的位置執行操做。好比,在處理路由時,先作 URL 解碼再進行路由匹配。

Japronto 已經可靠的實現了下面這些功能:

實現 HTTP 1.x 而且支持分片上傳
完整支持 HTTP 流水線
可配置是否讓連接 Keep-alive
支持同步和異步視圖
Master-multiworker 多任務處理
代碼熱加載
簡單易用的路由規則

結束語

上面提到的全部技術不僅適用於 Python,也一樣能夠被應用到其它語言,如 Ruby、JavaScript,甚至 PHP 等。

在此要感謝 Python 社區爲優化性能所付出的持續投入。尤爲是 Victor Stinner @VictorStinner、INADA Naoki @methane 和 Yury Selivanov @1st1 以及整個 PyPy 團隊。

獻給我摯愛的 Python。

原文連接:
https://blog.csdn.net/sinat_38682860/article/details/72862220


 

用 Python 實現每秒處理 120 萬次 HTTP 請求

識別圖中二維碼,領取python全套視頻資料

相關文章
相關標籤/搜索