JVM上的高并发HTTP客户端
HTTP可能是最流行的應用程序級別協議,并且有許多庫在網絡I / O之上實現它,這是常規I / O的一種特殊(面向流)情況。 由于所有I / O都有很多共同點1 ,所以讓我們開始對其進行一些討論。
我將集中討論具有大量并發HTTP請求的I / O情況,例如微服務,其中一組較高級別的HTTP服務會調用多個較低級別的HTTP服務,其中一些并發調用是由于數據依賴性而依次執行的。
當滿足許多此類請求時,同時打開的連接總數有時可能會很大。 如果存在數據依賴性,或者較低級別的服務速度較慢(或由于特殊情況而減慢了速度)。 因此,微服務層往往需要許多并發的,可能長期存在的連接。 看看我們有多少打開的連接需要支持沒有崩潰讓我們回憶一下利特爾法則2Ψ是正在進行中的請求數,ρ是平均到達率和τ是平均完成時間的平均值:
Ψ=ρτ
我們可以支持的處理中請求的數量取決于語言運行時,操作系統和硬件; 平均請求完成時間(或延遲)取決于我們為滿足請求所必須執行的操作,當然包括對任何較低級別服務的調用,對存儲的訪問等。
我們可以支持多少個并發HTTP請求? 每個組件都需要一個開放的連接和一些可運行的原語,這些原語可以使用syscalls對其進行讀寫。 如果內存,I / O子系統和網絡帶寬可以保持同步,則現代OS可以支持數十萬個開放的TCP連接。 它們提供的用于套接字的可運行基元是線程 。 線程比套接字要重得多:運行現代操作系統的單個盒子只能支持5000-15000個線程。
從10,000英尺開始:JVM上的I / O性能
如今,JDK線程是大多數平臺3上的OS線程,但是如果在任何時候只有很少的并發連接,那么“每個連接的線程”模型就可以了。
如果沒有呢? 這個問題的答案隨著歷史的變化而改變:
- JDK 1.4之前的版本僅具有調用操作系統的線程阻塞I / O的庫( java.io pkgs),因此只能使用“每個連接線程”模型或線程池4 。 如果您想要更好的東西,可以通過JNI來利用操作系統的其他功能。
- JDK 1.4添加了非阻塞I / O或NIO( java.nio包),僅在可以立即完成連接時才可以從連接讀取/寫入數據,而不必讓線程進入睡眠狀態。 更重要的是,它增加了一種方法,使一個線程可以通過套接字選擇在多個通道上有效工作,這意味著要求OS阻止當前線程,并在有可能立即從至少一個套接字中立即接收/發送數據時取消阻止該線程。一套。
- JDK 1.7添加了NIO.2,也稱為異步I / O(仍為java.nio軟件包)。 這意味著要求操作系統僅在I / O完成后才在后臺完全執行I / O任務,并在稍后喚醒帶有通知的線程。
從JVM調用HTTP
JVM提供了多種開源HTTP客戶端庫。 線程阻塞API易于使用和維護,但對于許多并發請求而言可能效率較低,而異步請求有效但較難使用。 異步API也會通過異步對代碼產生病毒影響:消耗異步數據的任何方法本身都必須是異步的,或者阻止并抵消了異步的優勢。
以下是Java和Clojure的開源HTTP客戶端的選擇:
- Java
- JDK的URLConnection使用傳統的線程阻塞I / O。
- Clojure
- clj-http包裝Apache HTTP客戶端。
從10,000英尺開始:輕松
由于Java線程占用大量資源 ,因此,如果要執行I / O并擴展到許多并發連接,則必須使用NIO或異步NIO。 另一方面,它們很難編碼和維護。 有解決這個難題的方法嗎?
如果線程不繁重,我們只能使用直接的阻塞I / O,那么我們的問題確實是: 我們是否可以擁有足夠便宜的線程 ,并且可以創建比OS線程大得多的線程?
目前,JVM本身不提供輕量級線程,但Quasar借助纖維 (在用戶空間中實現的非常有效的線程)來進行救援。
從JVM調用HTTP
Comsat將現有的某些庫與Quasar纖維集成在一起。 Comsat API與原始API相同,“ HTTP客戶端”部分 )說明了如何將其掛鉤; 其余的只需確保您正確運行Quasar ,在需要執行新的HTTP調用時啟動光纖,并使用以下一個(或多個)光纖阻塞API(或從模板和示例中汲取靈感:
- Java的 :
- Apache HTTP客戶端 API的廣泛子集,通過橋接異步者集成。
- Clojure :
- clj-http API的廣泛子集,通過橋接http-kit的異步API進行集成。
可以輕松添加新的集成 ,當然也總是歡迎您提供幫助。
JBender的一些負載測試
jbender是Pinterest基于Quasar的網絡負載測試框架。 它高效而靈活,但是由于Quasar光纖阻塞,其源代碼很小且可讀性強。 使用它就像使用傳統的線程阻塞I / O一樣簡單。
考慮這個項目 , 該項目建立在JBender之上,并以少量代碼為所有Comsat集成庫提供HTTP負載測試客戶端,無論是其原始線程阻塞版本還是Comsat的光纖阻塞版本。
JBender可以使用任何(普通,重量級,OS)線程或光纖來執行請求,它們均被Quasar抽象到一個稱為Strand的共享抽象類中,因此線程阻塞和光纖阻塞版本共享HTTP代碼:這證明了Comsat集成的API與原始API完全相同,并且光纖和線程的使用方式完全相同。
負載測試客戶端接受參數以自定義其運行的幾乎每個方面,但是我們將考慮的測試案例如下:
所有測試均針對運行Dropwizard的服務器觸發,該服務器經過優化,可在HTTP服務器端使用帶comsat-dropwizard以實現最大的并發性。 服務器僅用“ Hello!”答復任何請求。
以下是有關我們的負載測試環境的一些信息:
第一個重要的結果是, 基于Comsat的客戶端在不使用光纖模式的情況下贏得了成功 。 Apache用于許多持久連接,而OkHttp用于許多短期請求,這些請求具有很高的目標速率,堆的大小也較小(分別為990 MiB和3 GiB,為簡潔起見僅顯示第一個):
OkHttp在快速請求的速度和內存利用率方面表現出色。 JVM的光纖版本使用異步API,并且即使底層機制是線程池提供的傳統阻塞I / O,其性能也顯著提高。
更令人印象深刻的是基于http-kit的光纖阻塞comsat-httpkit擊敗傳統clj-http客戶端的方法(仍然顯示出很小的堆):
也有其他Jersey提供程序(Grizzly,Jetty和Apache),但Jersey證明是最差的,其占用空間通常更大,并且異步接口(由Comsat的光纖阻塞集成使用)不幸地為每個線程生成并阻塞了一個線程。每個請求; 由于這個原因(可能還取決于每個提供商的實施策略),光纖版本有時會提供明顯的性能優勢,而有時卻沒有。 無論如何,這些數字并不像Apache,OkHttp和http-kit那樣有趣,因此我不在這里包括它們,但是請讓我知道您是否希望看到它們。
(可選)從100 <10,000英尺開始:有關JVM上I / O性能的更多信息
因此,您想知道為什么在高并發情況下光纖比線程更好。
當只有少數并發套接字打開時,OS內核可以以非常低的延遲喚醒被阻塞的線程。 但是OS線程是通用的,并且在許多用例中會增加可觀的開銷:它們消耗大量內核內存用于簿記,同步syscall可能比過程調用慢幾個數量級, 上下文切換昂貴 ,并且調度算法過于籠統。 。 所有這些都意味著,對于具有大量通信和同步的細粒度并發,或者對于一般來說高度并發的系統6而言,當前OS線程并不是最佳選擇。
阻止I / O系統調用確實可以無限期地阻止昂貴的OS線程,因此,當您為大量并發連接提供服務時,“每個連接線程”方法將很快導致系統崩潰。 另一方面,使用線程池可能會使“可接受的”連接隊列溢出,因為我們無法保持到達速度或至少導致不可接受的延遲。 相反,“每連接光纖”方法是完全可持續的,因為光纖非常輕便。
總結一下 :線程可以通過較少的并發連接來改善延遲,而光纖可以在有許多并發連接的情況下改善吞吐量。
當然,光纖需要在活動的OS線程之上運行,因為OS對光纖一無所知,因此Quasar在線程池上調度了光纖。 Quasar只是一個庫,并且完全在用戶空間中運行,這意味著執行syscall的光纖將在整個調用持續時間內阻塞其底層的JVM線程,從而使其他光纖無法使用它。 這就是為什么這樣的調用要盡可能短的原因,尤其是它們不應該等待很長時間甚至無限期地等待是很重要的:在實踐中,光纖應該只執行非阻塞的系統調用。 那么,如何使阻塞的HTTP客戶端在光纖上運行得如此好呢? 由于這些庫還提供了非阻塞(但不方便)的API,因此我們將該異步API轉換為光纖阻塞的API,并使用它來實現原始的阻塞API。 新的實現(非常簡短,只不過是一個包裝器)將:
從光纖(和程序員)的角度來看,當I / O完成時,執行將在庫調用之后重新開始,就像使用線程和常規線程阻塞調用時一樣。
包起來
使用Quasar和Comsat,您可以使用Java,Clojure或Kotlin輕松編寫和維護高度并發且HTTP密集的代碼,甚至可以選擇自己喜歡的HTTP客戶端庫,而無需任何API鎖定。 您還想使用其他東西嗎? 讓我們知道,或者自己將其與Quasar集成。
翻譯自: https://www.javacodegeeks.com/2015/12/high-concurrency-http-clients-jvm.html
總結
以上是生活随笔為你收集整理的JVM上的高并发HTTP客户端的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是财务制度备案制度(什么是财务制度备
- 下一篇: 安卓出的手机叫什么名字(安卓出的手机)