博科光纤交换机java_带有光纤的可扩展,健壮和标准的Java Web服务
博科光纖交換機java
這篇博客文章討論了負(fù)載下的基準(zhǔn)Web服務(wù)性能。 要了解有關(guān)Web服務(wù)性能理論的更多信息,請閱讀利特爾定律,可伸縮性和容錯 。
使用阻塞和異步IO對Web服務(wù)進行基準(zhǔn)測試
Web應(yīng)用程序(或Web服務(wù))如何在負(fù)載下,面對各種故障時以及在兩種情況的組合下表現(xiàn)如何,這是我們代碼最重要的特性-當(dāng)然是正確的。 由于Web服務(wù)通常執(zhí)行非常常見的操作-詢問緩存,數(shù)據(jù)庫或其他Web服務(wù)以收集數(shù)據(jù),將其組合并返回給調(diào)用方-因此,這種行為主要取決于Web框架/服務(wù)器及其架構(gòu)的選擇。 在先前的博客文章中 ,我們討論了利特爾定律,并將其應(yīng)用于分析Web服務(wù)器采用的不同體系結(jié)構(gòu)方法的理論限制。 這篇文章(對該文章的補充)重新討論了同一主題,只是這次我們將在實踐中衡量績效。
Web框架(我用這個術(shù)語來指代任何通過運行用戶代碼來響應(yīng)HTTP請求的軟件環(huán)境,無論是被稱為框架,應(yīng)用程序服務(wù)器,Web容器,還是語言的標(biāo)準(zhǔn)庫的一部分),都選擇以下一種兩種架構(gòu)。 首先是分配一個OS線程,該線程將運行我們的所有代碼,直到請求完成。 這是標(biāo)準(zhǔn)Java servlet , Ruby , PHP和其他環(huán)境所采用的方法。 這些服務(wù)器中的某些服務(wù)器在單個線程中運行所有用戶代碼,因此它們一次只能處理一個請求。 其他人在不同的并發(fā)線程上運行并發(fā)請求。 這種稱為“每個請求線程”的方法需要非常簡單的代碼。
另一種方法是對一個或多個OS線程(盡可能使用比并發(fā)請求數(shù)更少的OS線程)使用異步IO并盡可能多地將請求處理代碼調(diào)度到多個并發(fā)請求。 這是Node.js ,Java 異步servlet和JVM框架(如Vert.x和Play)采用的方法 。 據(jù)推測,這種方法的優(yōu)點是(這正是我們要衡量的)更好的可伸縮性和魯棒性(面對使用率高峰,失敗等),但是為此類異步服務(wù)器編寫代碼比為線程編寫代碼更復(fù)雜。每個請求的。 代碼的復(fù)雜程度取決于使用各種“回調(diào)地獄緩解”技術(shù)(例如promise和/或其他通常涉及monad的功能編程方法)的使用。
其他環(huán)境則試圖將兩種方法的優(yōu)點結(jié)合起來。 在幕后,他們使用異步IO,但是他們沒有讓程序員使用回調(diào)或monad,而是為程序員提供了光纖 (又稱輕量級線程或用戶級線程),這些光纖消耗很少的RAM并且阻塞開銷可以忽略不計。 這樣,這些環(huán)境在保持同步(阻塞)代碼的簡單性和熟悉性的同時,具有與異步方法相同的可伸縮性/性能/魯棒性優(yōu)點。 這樣的環(huán)境包括Erlang , Go和Quasar (將纖維添加到JVM)。
基準(zhǔn)測試
- 完整的基準(zhǔn)測試項目可以在這里找到。
為了測試兩種方法的相對性能,我們將使用一個簡單的Web服務(wù),該Web服務(wù)是使用JAX-RS API用Java編寫的。 測試代碼將模擬微服務(wù)的現(xiàn)代通用體系結(jié)構(gòu),但結(jié)果絕不限于使用微服務(wù)。 在微服務(wù)架構(gòu)中,客戶端(Web瀏覽器,手機,機頂盒)將請求發(fā)送到單個HTTP端點。 然后,該請求由服務(wù)器分解成幾個(通常是很多)其他子請求,這些子請求被發(fā)送到各種內(nèi)部HTTP服務(wù),每個子服務(wù)負(fù)責(zé)提供一種類型的數(shù)據(jù)或執(zhí)行一種操作(例如,一個微服務(wù)可負(fù)責(zé)返回用戶個人資料,另一個微服務(wù)負(fù)責(zé)返回其朋友圈)。
我們將對單個主服務(wù)進行基準(zhǔn)測試,該主服務(wù)向另一個或兩個其他微服務(wù)發(fā)出調(diào)用,并檢查當(dāng)微服務(wù)正常運行或發(fā)生故障時主服務(wù)的行為。
將通過安裝在http://ourserver:8080/internal/foo的此簡單服務(wù)來模擬微服務(wù):
@Singleton @Path("/foo") public class SimulatedMicroservice {@GET@Produces("text/plain")public String get(@QueryParam("sleep") Integer sleep) throws IOException, SuspendExecution, InterruptedException {if (sleep == null || sleep == 0)sleep = 10;Strand.sleep(sleep); // <-- Why we use Strand.sleep rather than Thread.sleep will be made clear laterreturn "slept for " + sleep + ": " + new Date().getTime();} }它所做的就是使用一個sleep查詢參數(shù),該參數(shù)指定服務(wù)在完成之前應(yīng)Hibernate的時間(以毫秒為單位)(至少10毫秒)。 這可以模擬可能需要很長時間(或很短時間)才能完成的遠程微服務(wù)。
為了模擬負(fù)載,我們使用了Photon , Photon是一種非常簡單的負(fù)載生成工具,使用Quasar光纖以相對較少的協(xié)調(diào)遺漏的方式發(fā)出大量并發(fā)請求并測量其延遲:每個請求都是由新生成的請求發(fā)送的纖維,然后依次以恒定速率生成纖維。
我們在三種不同的嵌入式Java Web服務(wù)器上測試了該服務(wù): Jetty , Tomcat (嵌入式)和Undertow (為JBoss Wildfly應(yīng)用程序服務(wù)器提供動力的Web服務(wù)器)。 現(xiàn)在,由于所有三個服務(wù)器均符合Java標(biāo)準(zhǔn),因此我們?yōu)樗腥齻€服務(wù)器重用了相同的服務(wù)代碼。 不幸的是,沒有用于以編程方式配置Web服務(wù)器的標(biāo)準(zhǔn)API,因此,基準(zhǔn)測試項目中的大多數(shù)代碼都簡單地抽象出了三臺服務(wù)器的不同配置API(位于JettyServer , TomcatServer和UndertowServer類中)。 Main類僅解析命令行參數(shù),配置嵌入式服務(wù)器,并將Jersey設(shè)置為JAX-RS容器。
我們已經(jīng)在c3.8xlarge EC2實例上運行了Load Generator和服務(wù)器,分別運行了Ubunto Server 14.04 64位和JDK8。如果您想自己使用基準(zhǔn)測試,請按照此處的說明進行操作。
此處顯示的結(jié)果是在Jetty上進行測試時獲得的結(jié)果。 Tomcat對普通阻止代碼的響應(yīng)類似,但是使用光纖時,其響應(yīng)性比Jetty差(這需要進一步研究)。 Undertow的行為與之相反:使用光纖時,其性能與Jetty相似,但是當(dāng)線程阻塞代碼面臨高負(fù)載時,崩潰很快。
配置操作系統(tǒng)
因為我們將在高負(fù)載下測試我們的服務(wù),所以需要一些配置才能在操作系統(tǒng)級別上支持它。
我們的/etc/sysctl.conf將包含
net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 1 net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_syncookies = 0 net.ipv4.ip_local_port_range = 1024 65535并因此被加載:
sudo sysctl -p /etc/sysctl.conf/etc/security/limits.conf將包含
* hard nofile 200000 * soft nofile 200000配置垃圾收集
大多數(shù)Java垃圾收集器都是基于生成假設(shè)的 ,該假設(shè)假設(shè)大多數(shù)對象的壽命都非常短。 但是,當(dāng)我們開始使用(模擬的)失敗的微服務(wù)測試系統(tǒng)時,它將生成持續(xù)數(shù)秒的開放連接,然后才斷開。 這種“中等壽命”(即也不短,但也不能太長)是最糟糕的一種垃圾。 看到默認(rèn)的GC導(dǎo)致了令人無法接受的暫停,并且不想浪費太多時間來微調(diào)GC之后,我們選擇嘗試使用HotSpot的新(ish)G1垃圾收集器。 我們要做的就是選擇一個最大暫停時間目標(biāo)(我們選擇了200ms)。 G1表現(xiàn)出色(1),因此我們沒有花更多時間調(diào)整收集器。
基準(zhǔn)同步方法
這是我們的待測服務(wù)代碼,從同步方法開始,該代碼安裝在/api/service 。 (完整的類,其中還包括HTTP客戶端的配置,可以在此處找到):
@Singleton @Path("/service") public class Service extends HttpServlet {private final CloseableHttpClient httpClient;private static final BasicResponseHandler basicResponseHandler = new BasicResponseHandler();public Service() {httpClient = HttpClientBuilder.create()... // configure.build();}@GET@Produces("text/plain")public String get(@QueryParam("sleep") int sleep) throws IOException {// simulate a call to a service that always completes in 10 ms - service AString res1 = httpClient.execute(new HttpGet(Main.SERVICE_URL + 10), basicResponseHandler);// simulate a call to a service that might fail and cause a delay - service BString res2 = sleep > 0 ? httpClient.execute(new HttpGet(Main.SERVICE_URL + sleep), basicResponseHandler) : "skipped";return "call response res1: " + res1 + " res2: " + res2;} }然后,我們的服務(wù)會調(diào)用一個或兩個其他微服務(wù),我們可以將它們命名為A和B(當(dāng)然,兩者都是由SimulatedMicroservice )。 雖然服務(wù)A總是需要10毫秒才能完成,但是可以模擬服務(wù)B以顯示變化的延遲。
假設(shè)服務(wù)B正常運行,并在工作10毫秒后返回其結(jié)果。 這是我們的服務(wù)隨時間推移每秒響應(yīng)1000個請求的方式(服務(wù)器使用2000個線程池)。 紅線是同時需要兩種微服務(wù)的請求的延遲,綠線是僅觸發(fā)對微服務(wù)A的調(diào)用的請求的延遲:
我們甚至可以將速率提高到3000Hz:
超過3000Hz,服務(wù)器會遇到嚴(yán)重困難。
現(xiàn)在,我們假設(shè)在某個時刻,服務(wù)B發(fā)生故障,導(dǎo)致服務(wù)B以大大增加的延遲進行響應(yīng)。 比方說5000毫秒 如果每秒我們在服務(wù)器上觸發(fā)300個觸發(fā)服務(wù)A和B的請求,以及另外10個僅觸發(fā)A(這是控制組)的請求,則該服務(wù)將按應(yīng)有的方式執(zhí)行:觸發(fā)B的那些請求會增加延遲,但是繞過它的人不受影響。
但是,如果我們隨后將請求速率提高到400Hz,則會發(fā)生不好的情況:
這里發(fā)生了什么? 當(dāng)服務(wù)B失敗時,觸發(fā)主服務(wù)的對主服務(wù)的請求將長時間阻塞,它們中的每一個都持有一個線程,直到請求完成,該線程才能返回到服務(wù)器的線程池。 線程開始堆積,直到它們耗盡服務(wù)器的線程池為止,此時,沒有請求-甚至沒有嘗試使用失敗的服務(wù)的請求-都無法通過,服務(wù)器實質(zhì)上崩潰了。 這被稱為級聯(lián)故障 。 單個失敗的微服務(wù)可以關(guān)閉整個應(yīng)用程序。 我們怎樣做才能減輕這種故障?
我們可以嘗試進一步增加最大線程池大小,但最大限制為(相當(dāng)?shù)?#xff09;。 OS線程給系統(tǒng)帶來了兩種負(fù)擔(dān):第一,它們的堆棧消耗相對大量的RAM;第二,它們的堆棧占用大量RAM。 使用該RAM來存儲數(shù)據(jù)緩存的響應(yīng)式應(yīng)用程序要好得多。 其次,將多個線程調(diào)度到相對較少的CPU內(nèi)核上會增加不可忽略的開銷。 如果服務(wù)器僅執(zhí)行很少的CPU密集型計算(通常是這種情況;服務(wù)器通常只是從其他來源收集數(shù)據(jù)),則調(diào)度開銷可能會變得很大。
當(dāng)我們將線程池大小增加到5000時,服務(wù)器性能會更好。 在500Hz的頻率下,它仍然運行良好:
在700 Hz時,它搖搖欲墜:
…并在我們增加費率時崩潰。 但是,一旦我們將線程池大小增加到6000,其他線程便無濟于事。 這是在1100Hz下具有6000個線程的服務(wù)器:
這里有7000個線程,處理相同的負(fù)載:
我們可以嘗試在微服務(wù)調(diào)用上設(shè)置超時。 超時始終是一個好主意,但是選擇什么超時值? 太低了,可能會使我們的應(yīng)用程序無法使用。 太高,我們還沒有真正解決問題。
我們還可以安裝一個斷路器,例如Netfilx的Hystrix ,它將試圖Swift發(fā)現(xiàn)問題并隔離發(fā)生故障的微服務(wù)。 像超時一樣,斷路器始終是一個好主意,但是如果我們可以顯著提高電路的容量,我們可能應(yīng)該這樣做(并且為了安全起見,仍然要安裝斷路器)。
現(xiàn)在讓我們看看異步方法的發(fā)展。
對異步方法進行基準(zhǔn)測試
異步方法不為每個連接分配線程,而是使用少量線程來處理大量IO事件。 Servlet標(biāo)準(zhǔn)現(xiàn)在除了阻塞API之外還支持異步API,但是由于沒有人喜歡回調(diào)(特別是在具有共享可變狀態(tài)的多線程環(huán)境中),因此很少有人使用它。 Play框架還具有異步API,為了減輕與異步代碼始終相關(guān)的某些痛苦,Play用功能性編程的monadic組合替換了簡單的回調(diào)。 Play API不僅是非標(biāo)準(zhǔn)的,對于Java開發(fā)人員來說也感覺很陌生。 這也無助于減少與無法避免競爭條件的環(huán)境中運行異步代碼相關(guān)的問題。 簡而言之,異步代碼是一團糟。
但是,我們?nèi)匀豢梢允褂霉饫w測試這種方法的行為,同時保持我們的代碼美觀,簡單和阻塞。 我們?nèi)詫⑹褂卯惒絀O,但丑陋將對我們完全隱藏。
對
Comsat是一個開源項目,將標(biāo)準(zhǔn)或流行的Web相關(guān)API與Quasar光纖集成在一起。 這是我們的服務(wù),現(xiàn)在利用Comsat( 此處為全班制):
@Singleton @Path("/service") public class Service extends HttpServlet {private final CloseableHttpClient httpClient;private static final BasicResponseHandler basicResponseHandler = new BasicResponseHandler();public Service() {httpClient = FiberHttpClientBuilder.create() // <---------- FIBER....build();}@GET@Produces("text/plain")@Suspendable // <------------- FIBERpublic String get(@QueryParam("sleep") int sleep) throws IOException {// simulate a call to a service that always completes in 10 ms - service AString res1 = httpClient.execute(new HttpGet(Main.SERVICE_URL + 10), basicResponseHandler);// simulate a call to a service that might fail and cause a delay - service BString res2 = sleep > 0 ? httpClient.execute(new HttpGet(Main.SERVICE_URL + sleep), basicResponseHandler) : "skipped";return "call response res1: " + res1 + " res2: " + res2;} }該代碼與我們的線程阻塞服務(wù)相同,除了幾行(用箭頭標(biāo)記)和Main類中的一行。
當(dāng)B正確執(zhí)行時,一切都很好(當(dāng)服務(wù)器處理前幾個請求時,您會在控制臺上看到一些警告,提示光纖占用了太多的CPU時間。沒關(guān)系。這只是執(zhí)行的初始化代碼):
事不宜遲,以下是我們的光纖服務(wù)(使用40個OS線程,這是Jetty的最小線程池大小),頻率為3000Hz:
在5000Hz時:
在6000Hz時,需要一些時間才能完全預(yù)熱,但隨后會收斂:
現(xiàn)在,讓我們踢出問題的微服務(wù),即我們親愛的服務(wù)B,使其經(jīng)歷5秒的延遲。 這是我們的服務(wù)器,頻率為1000Hz:
在2000Hz時:
使用錯誤的服務(wù)B響應(yīng)請求時,除了偶爾出現(xiàn)峰值外,航行仍然平穩(wěn),但是僅撞到A的人什么也沒有。 在4000Hz時,它開始顯示出一些明顯的但不是災(zāi)難性的抖動:
每秒需要處理5000個請求(在失敗條件下!),以使服務(wù)器無響應(yīng)。 糟糕的是,服務(wù)B可能會導(dǎo)致20秒的延遲,但我們的服務(wù)器仍然可以每秒處理1500次觸發(fā)失敗服務(wù)的請求,而那些未達到故障服務(wù)的請求甚至都不會注意到:
那么,這是怎么回事? 當(dāng)服務(wù)B開始顯示很高的延遲時,為調(diào)用B的請求服務(wù)的光纖會堆積一段時間,但是由于我們可以擁有這么多的光纖,并且由于它們的開銷如此之低,系統(tǒng)很快就達到了一個新的穩(wěn)態(tài)-成千上萬的阻塞光纖,但這完全可以!
進一步擴大我們的能力
因為我們的Web服務(wù)向微服務(wù)發(fā)出傳出請求,并且因為我們現(xiàn)在可以服務(wù)很多并發(fā)請求,所以我們的服務(wù)最終可能會遇到另一個操作系統(tǒng)限制。 每個傳出的TCP套接字都捕獲一個臨時端口 。 我們已經(jīng)將net.ipv4.ip_local_port_range設(shè)置為1024 65535 ,總共65535 – 1024 = 64511傳出連接,但是我們的服務(wù)可以處理更多內(nèi)容。 不幸的是,我們不能再提高此限制,但是由于此限制是針對每個網(wǎng)絡(luò)接口的,因此我們可以定義虛擬接口 ,并讓傳出請求隨機或基于某種邏輯選擇一個接口。
結(jié)論
光纖使用戶能夠享受異步IO,同時保持簡單和標(biāo)準(zhǔn)的代碼。 因此,我們通過異步IO獲得的好處不是減少延遲(我們尚未進行基準(zhǔn)測試,但是沒有理由相信它比純線程阻塞IO更好),但是容量顯著增加。 系統(tǒng)的穩(wěn)定狀態(tài)支持更高的負(fù)載。 異步IO可以更好地利用硬件資源。
當(dāng)然,這種方法也有缺點。 其中最主要的(實際上,我認(rèn)為這是唯一的)是庫集成。 我們在光纖上調(diào)用的每個阻塞API都必須專門支持光纖。 順便說一下,這并不是僅輕量級線程方法所獨有的:要使用異步方法,所有使用的IO庫也必須是異步的。 實際上,如果庫具有異步API,則可以輕松地將其轉(zhuǎn)換為光纖阻塞的API。 Comsat項目是一組將標(biāo)準(zhǔn)或流行的IO API與Quasar光纖集成在一起的模塊。 Comsat的最新版本支持servlet,JAX-RS服務(wù)器和客戶端以及JDBC。 即將發(fā)布的版本(以及基準(zhǔn)測試中使用的版本)將增加對Apache HTTP客戶端,Dropwizard,JDBI,Retrofit以及可能的jOOQ的支持。
翻譯自: https://www.javacodegeeks.com/2015/04/scalable-robust-and-standard-java-web-services-with-fibers.html
博科光纖交換機java
總結(jié)
以上是生活随笔為你收集整理的博科光纤交换机java_带有光纤的可扩展,健壮和标准的Java Web服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 格力安卓对学历要求(格力安卓)
- 下一篇: java自动推断类型_推断:Facebo