在生产中运行Java:SRE的观点
作為站點(diǎn)可靠性工程師 (SRE),我確保我們的生產(chǎn)服務(wù)高效,可擴(kuò)展且可靠。 典型的SRE是生產(chǎn)大師,必須對(duì)更廣泛的體系結(jié)構(gòu)有很好的了解,并精通許多更精細(xì)的細(xì)節(jié)。
SRE是會(huì)說(shuō)多種語(yǔ)言的程序員,這是很常見(jiàn)的,他們希望能夠理解多種不同的語(yǔ)言。 例如,C ++可能很難編寫(xiě),測(cè)試和正確使用,但具有高性能,非常適合諸如數(shù)據(jù)庫(kù)之類(lèi)的后端系統(tǒng)。 Python很容易編寫(xiě),非常適合快速編寫(xiě)腳本,對(duì)自動(dòng)化很有用。 Java位于中間位置,盡管它是一種編譯語(yǔ)言,但它提供了類(lèi)型安全性,性能以及許多其他優(yōu)點(diǎn),使其成為編寫(xiě)Web基礎(chǔ)結(jié)構(gòu)的理想選擇。
盡管SRE采用的許多最佳實(shí)踐可以推廣到任何語(yǔ)言,但是Java還是存在一些獨(dú)特的挑戰(zhàn)。 本文計(jì)劃重點(diǎn)介紹其中一些,并討論我們可以采取哪些措施來(lái)解決這些問(wèn)題。
部署方式
一個(gè)典型的Java應(yīng)用程序由數(shù)百個(gè)類(lèi)文件組成,這些類(lèi)文件由您的團(tuán)隊(duì)編寫(xiě),或者由應(yīng)用程序依賴的通用庫(kù)編寫(xiě)。 為了控制類(lèi)文件的數(shù)量,并提供更好的版本控制和分隔,通常將它們捆綁到JAR或WAR文件中。
托管Java應(yīng)用程序的方法有很多,一種流行的方法是使用Java Servlet容器,例如Tomcat或JBoss 。 從理論上講,它們提供了一些通用的Web基礎(chǔ)結(jié)構(gòu)和庫(kù),以使其更易于部署和管理Java應(yīng)用程序。 以Tomcat(一個(gè)提供實(shí)際Web服務(wù)器并代表您加載應(yīng)用程序的Java程序)為例。 這在某些情況下可能會(huì)很好地起作用,但實(shí)際上會(huì)增加額外的復(fù)雜性。 例如,您現(xiàn)在需要跟蹤JRE版本,Tomcat版本和應(yīng)用程序版本。 測(cè)試不兼容性,并確保每個(gè)人都使用相同版本的完整堆??赡軙?huì)出現(xiàn)問(wèn)題,并導(dǎo)致細(xì)微的問(wèn)題。 Tomcat還帶來(lái)了自己的定制配置,這是另一回事。
一個(gè)很好的承租人是“ 保持簡(jiǎn)單 ”,但是在Servlet容器方法中,您必須跟蹤幾十個(gè)Tomcat文件,一個(gè)或多個(gè)組成應(yīng)用程序的WAR文件以及所有的Tomcat配置。隨之而來(lái)。
因此,有些框架通過(guò)嵌入自己的Web服務(wù)器,而不是托管在完整的應(yīng)用程序服務(wù)器中,試圖減少這種開(kāi)銷(xiāo)。 仍然有一個(gè)JVM,但它會(huì)調(diào)用一個(gè)JAR文件,其中包含運(yùn)行該應(yīng)用程序所需的所有內(nèi)容。 支持這些獨(dú)立應(yīng)用程序的流行框架是Dropwizard和Spring Boot 。 要部署該應(yīng)用程序的新版本,只需更改一個(gè)文件,然后重新啟動(dòng)JVM。 這在開(kāi)發(fā)和測(cè)試應(yīng)用程序時(shí)也很有用,因?yàn)槊總€(gè)人都在使用相同版本的堆棧。 對(duì)于回滾(SRE的核心工具之一)來(lái)說(shuō),它也特別有用,因?yàn)橹恍韪囊粋€(gè)文件即可(可以快速更改符號(hào)鏈接)。
Tomcat樣式的WAR文件要注意的一件事是,該文件將包含應(yīng)用程序類(lèi)文件以及該應(yīng)用程序依賴于JAR文件的所有庫(kù)。 在獨(dú)立方法中,所有依賴項(xiàng)都合并到單個(gè)Fat JAR中 。 單個(gè)JAR文件,其中包含整個(gè)應(yīng)用程序的類(lèi)文件。 這些Fat或Uber JAR不僅易于版本化和復(fù)制(因?yàn)樗菃蝹€(gè)不可變文件),而且由于對(duì)依賴項(xiàng)中未使用的類(lèi)進(jìn)行修剪而實(shí)際上可以小于等效的WAR文件。
通過(guò)不需要單獨(dú)的JVM和JAR文件,甚至可以更進(jìn)一步。 實(shí)際上,例如capsule.io之類(lèi)的工具可以將JAR文件,JVM和所有配置捆綁到一個(gè)可執(zhí)行文件中。 現(xiàn)在,我們可以真正確保整個(gè)堆棧都使用相同的版本,并且部署與服務(wù)器上可能已經(jīng)安裝的內(nèi)容無(wú)關(guān)。
保持簡(jiǎn)單,并使用單個(gè)Fat JAR或可能的可執(zhí)行文件,使應(yīng)用程序的版本快速便捷。
啟動(dòng)
即使Java是一種編譯語(yǔ)言,它也不會(huì)編譯為機(jī)器代碼,而是會(huì)編譯為字節(jié)碼。 在運(yùn)行時(shí),Java虛擬機(jī)(JVM)解釋字節(jié)碼,并以最有效的方式執(zhí)行它。 例如, 即時(shí) (JIT)編譯使JVM可以監(jiān)視應(yīng)用程序的使用方式,并即時(shí)將字節(jié)碼編譯為最佳機(jī)器代碼。 從長(zhǎng)遠(yuǎn)來(lái)看,這對(duì)應(yīng)用程序可能是有利的,但是在啟動(dòng)過(guò)程中,它可能會(huì)使應(yīng)用程序在數(shù)十分鐘或更長(zhǎng)時(shí)間內(nèi)處于次優(yōu)狀態(tài)。 需要注意的是,這對(duì)負(fù)載平衡,監(jiān)視,容量規(guī)劃等有影響。
在多服務(wù)器部署中,最佳實(shí)踐是將流量緩慢增加到新啟動(dòng)的任務(wù),使其有時(shí)間進(jìn)行預(yù)熱,并且不損害服務(wù)的整體性能。 在將新任務(wù)放入用戶服務(wù)路徑之前,您可能會(huì)通過(guò)向其發(fā)送人工流量來(lái)預(yù)熱新任務(wù)。 如果預(yù)熱過(guò)程無(wú)法逼近正常的用戶流量,則人工流量可能會(huì)出現(xiàn)問(wèn)題。 實(shí)際上,這種偽造的流量可能會(huì)觸發(fā)JIT對(duì)通常不會(huì)發(fā)生的情況進(jìn)行優(yōu)化,從而使應(yīng)用程序處于未達(dá)到最佳狀態(tài)或處于比未進(jìn)行JIT時(shí)更糟糕的狀態(tài)。
容量規(guī)劃時(shí)也應(yīng)考慮啟動(dòng)緩慢。 不要期望冷任務(wù)與熱任務(wù)處理相同的負(fù)載。 推出新版本的應(yīng)用程序時(shí),這一點(diǎn)很重要,因?yàn)橄到y(tǒng)的容量會(huì)下降,直到任務(wù)預(yù)熱。 如果不考慮這一點(diǎn),可能會(huì)同時(shí)重新加載太多任務(wù),從而導(dǎo)致基于容量的級(jí)聯(lián)中斷。
期待冷啟動(dòng),并嘗試以實(shí)際流量預(yù)熱應(yīng)用程序。
監(jiān)控方式
該建議是通用的監(jiān)視建議 ,但是對(duì)于Java值得重復(fù)。 確保從Java應(yīng)用程序?qū)С鲎钪匾妥钣杏玫亩攘繕?biāo)準(zhǔn),并對(duì)其進(jìn)行收集并輕松繪制圖形。 有許多工具和框架可用于導(dǎo)出指標(biāo),甚至還有更多的工具和框架可用于收集,匯總和顯示。
當(dāng)出現(xiàn)問(wèn)題時(shí),應(yīng)該僅從收集的指標(biāo)中排除故障即可 。 您不應(yīng)該依賴日志文件或查看代碼來(lái)處理中斷。
大多數(shù)中斷是由更改引起的。 也就是說(shuō),應(yīng)用程序的新版本,配置更改,新的流量來(lái)源,硬件故障或后端依賴關(guān)系的行為不同。 應(yīng)用程序?qū)С龅亩攘繕?biāo)準(zhǔn)應(yīng)包括識(shí)別Java版本,應(yīng)用程序和使用中的配置的方法。 它應(yīng)該分解流量,混合,錯(cuò)誤計(jì)數(shù)等的來(lái)源。還應(yīng)該跟蹤后端依賴項(xiàng)的運(yùn)行狀況,延遲,錯(cuò)誤率等。 在大多數(shù)情況下,這足以快速診斷出故障。
特定于Java的指標(biāo)可以幫助您了解應(yīng)用程序的運(yùn)行狀況和性能。 指導(dǎo)有關(guān)如何擴(kuò)展和優(yōu)化應(yīng)用程序的未來(lái)決策。 垃圾回收時(shí)間,堆大小,線程數(shù),JIT時(shí)間都很重要,并且特定于Java。
最后,關(guān)于測(cè)量響應(yīng)時(shí)間或等待時(shí)間的注釋。 也就是說(shuō),應(yīng)用程序處理請(qǐng)求所花費(fèi)的時(shí)間。 許多人會(huì)誤以為平均等待時(shí)間,部分原因是它很容易計(jì)算。 平均值可能會(huì)引起誤解 ,因?yàn)樗鼪](méi)有顯示分布的形狀 。 大多數(shù)請(qǐng)求可能會(huì)很快得到處理,但請(qǐng)求的尾部可能很少,但需要一段時(shí)間。 這對(duì)于JVM應(yīng)用程序尤其令人不安,因?yàn)樵诶厥者^(guò)程中,有一個(gè)“ 停止世界” (STW)階段,在此階段,應(yīng)用程序必須暫停以允許垃圾回收完成。 在此暫停中,不會(huì)響應(yīng)任何請(qǐng)求,并且用戶可能要等待幾秒鐘。
最好收集最大或99%(或更高)的百分比延遲。 對(duì)于百分位數(shù),也就是說(shuō),每100個(gè)請(qǐng)求中,有99個(gè)的送達(dá)速度快于該數(shù)字。 查看最壞情況下的延遲更有意義,并且更能反映用戶感知的性能。
衡量重要的指標(biāo),以后可以依靠。
內(nèi)存管理
您花費(fèi)大量時(shí)間來(lái)學(xué)習(xí)各種JVM垃圾收集算法 。 當(dāng)前的最新?tīng)顟B(tài)是并發(fā)收集器G1或CMS 。 您可以決定哪種方法最適合您的應(yīng)用程序,但目前G1可能是贏家。 有很多很棒的文章解釋了它們的工作方式,但我將介紹一些關(guān)鍵主題。
啟動(dòng)時(shí),Java虛擬機(jī)(JVM)通常會(huì)保留大量OS內(nèi)存,并將其分為堆和非堆。 非堆包含諸如Metaspace ( 正式稱為Permgen )和堆棧空間之類(lèi)的區(qū)域。 元空間用于類(lèi)定義,而堆棧空間用于每個(gè)線程的堆棧。 堆用于創(chuàng)建的對(duì)象,這些對(duì)象通常占用大部分內(nèi)存使用量。 與典型的可執(zhí)行文件不同,JVM具有-Xms和-Xmx標(biāo)志 ,用于控制堆的最小和最大大小。 這些限制限制了JVM將使用的最大RAM量,這可以使服務(wù)器上的內(nèi)存需求可預(yù)測(cè)。 通常將這兩個(gè)標(biāo)志設(shè)置為相同的值,以提供它們以填充服務(wù)器上的可用RAM。 還有一些針對(duì)Docker容器調(diào)整大小的最佳實(shí)踐。
垃圾回收(GC)是通過(guò)查找不再使用(即不再引用)且可以回收的Java對(duì)象來(lái)管理此堆的過(guò)程。 在大多數(shù)情況下,JVM會(huì)掃描對(duì)象的完整圖,并標(biāo)記找到的對(duì)象。 最后,將所有未訪問(wèn)的內(nèi)容刪除。 為了確保沒(méi)有競(jìng)爭(zhēng)狀況,GC通常必須停止運(yùn)行(STW),這會(huì)在應(yīng)用程序完成時(shí)將其暫停一會(huì)兒。
GC是(可能是不必要的)怨恨的根源,因?yàn)樗粴w咎于許多性能問(wèn)題。 通常,這歸結(jié)為不了解GC的工作原理。 例如,如果堆的大小太小,則JVM可以主動(dòng)進(jìn)行垃圾收集,從而徒勞地釋放空間。 然后,應(yīng)用程序可能會(huì)陷入“ GC崩潰 ”循環(huán)中,這在釋放空間方面幾乎沒(méi)有進(jìn)展,并且在GC中花費(fèi)了越來(lái)越多的時(shí)間,而不是運(yùn)行應(yīng)用程序代碼。
可能發(fā)生這種情況的兩種常見(jiàn)情況是內(nèi)存泄漏或資源耗盡 。 垃圾收集的語(yǔ)言不應(yīng)允許通常所說(shuō)的內(nèi)存泄漏,但是,它們可能會(huì)發(fā)生。 例如,維護(hù)一個(gè)永不過(guò)期的對(duì)象的緩存。 此緩存將永遠(yuǎn)增長(zhǎng),即使該緩存中的對(duì)象可能永遠(yuǎn)不會(huì)再使用,它??們?nèi)匀槐灰?#xff0c;因此不適合進(jìn)行垃圾回收。
另一個(gè)常見(jiàn)的情況是無(wú)限隊(duì)列 。 如果您的應(yīng)用程序?qū)魅氲恼?qǐng)求放置在無(wú)限制的隊(duì)列中,則該隊(duì)列可能永遠(yuǎn)增長(zhǎng)。 如果請(qǐng)求數(shù)量激增,保留在隊(duì)列中的對(duì)象可能會(huì)增加堆使用率,從而導(dǎo)致應(yīng)用程序在GC中花費(fèi)越來(lái)越多的時(shí)間。 因此,應(yīng)用程序?qū)⒂懈俚臅r(shí)間來(lái)處理來(lái)自隊(duì)列的請(qǐng)求,從而導(dǎo)致積壓量增加。 隨著GC努力尋找任何要釋放的對(duì)象,這逐漸失去控制,直到應(yīng)用程序無(wú)法取得任何進(jìn)展。
另外一個(gè)細(xì)節(jié)是,垃圾收集器算法具有許多優(yōu)化措施,可以嘗試減少總的GC時(shí)間。 一個(gè)重要的發(fā)現(xiàn)( 弱代假設(shè) )是對(duì)象要么存在很短時(shí)間(例如,與處理請(qǐng)求有關(guān)),要么存在很長(zhǎng)時(shí)間(例如,管理壽命長(zhǎng)的資源的全局對(duì)象)。
因此,堆進(jìn)一步劃分為年輕空間和舊空間。 在年輕空間中運(yùn)行的GC算法假定對(duì)象將被釋放,否則,GC會(huì)將對(duì)象提升到舊空間。 用于舊空間的算法做出了相反的假設(shè),即對(duì)象不會(huì)被釋放。 因此,也可以調(diào)整年輕人/老年人的大小,并且根據(jù)G1或CMS,方法將有所不同。 但是,如果年輕空間太小,那么應(yīng)該只存在很短時(shí)間的物體最終將被提升到舊空間。 打破了舊的GC算法所做的某些假設(shè),導(dǎo)致GC的運(yùn)行效率降低,并導(dǎo)致了諸如內(nèi)存碎片之類(lèi)的次要問(wèn)題。
如前所述,GC是長(zhǎng)尾延遲的來(lái)源,因此應(yīng)關(guān)閉監(jiān)視。 應(yīng)該記錄GC每個(gè)階段所花費(fèi)的時(shí)間,以及GC運(yùn)行之前和之后堆空間的滿度(按年輕/舊/等等分解)。 這提供了調(diào)優(yōu)或改進(jìn)應(yīng)用程序以控制GC所需的所有提示。
讓GC成為您的朋友。 應(yīng)該特別注意堆和垃圾收集器,并且應(yīng)該對(duì)其進(jìn)行調(diào)整(甚至是粗略地調(diào)整),以確保即使在滿載/最壞的情況下也有足夠的堆空間。
其他技巧
調(diào)試
Java有許多豐富的工具可用于在開(kāi)發(fā)和生產(chǎn)過(guò)程中進(jìn)行調(diào)試。 例如,可以捕獲正在運(yùn)行的應(yīng)用程序中的實(shí)時(shí)堆棧跟蹤和堆轉(zhuǎn)儲(chǔ)。 這對(duì)于了解內(nèi)存泄漏或死鎖很有用。 但是,通常必須確保應(yīng)用程序已啟動(dòng)以允許這些功能,并且典型工具jmap , jcmd等在服務(wù)器上實(shí)際可用。 在Docker容器或非標(biāo)準(zhǔn)環(huán)境中運(yùn)行應(yīng)用程序可能會(huì)使此操作更加困難,因此請(qǐng)測(cè)試并編寫(xiě)一本有關(guān)如何執(zhí)行此操作的手冊(cè)。
許多框架還通過(guò)Web服務(wù)公開(kāi)了許多此類(lèi)信息,以便于調(diào)試,例如Dropwizard / threads資源或Spring Boot生產(chǎn)端點(diǎn) 。
不要等到出現(xiàn)生產(chǎn)問(wèn)題后,立即測(cè)試如何獲取堆轉(zhuǎn)儲(chǔ)和堆棧跟蹤。
更少但更大的任務(wù)
JVM的許多功能對(duì)每個(gè)運(yùn)行的JVM都有固定的成本,例如JIT和垃圾回收。 您的應(yīng)用程序也可能具有固定的開(kāi)銷(xiāo),例如資源輪詢(后端數(shù)據(jù)庫(kù)連接)等。如果運(yùn)行較少但較大的實(shí)例(就CPU和RAM而言),則可以減少此固定成本,從而實(shí)現(xiàn)規(guī)模經(jīng)濟(jì)。 我已經(jīng)看到Java應(yīng)用程序擁有的CPU和RAM數(shù)量增加了一倍,使它每秒能夠處理4倍的請(qǐng)求(不影響延遲)。 但是,這對(duì)應(yīng)用程序以多線程方式進(jìn)行擴(kuò)展的能力做出了一些假設(shè),但是通常垂直擴(kuò)展比水平擴(kuò)展容易。
使您的JVM盡可能大。
32位和64位Java
如果您的應(yīng)用程序使用的RAM不超過(guò)4GiB,則通常會(huì)運(yùn)行32位JVM。 這是因?yàn)?2位指針的大小是64位大小的一半,這減少了每個(gè)Java對(duì)象的開(kāi)銷(xiāo)。 但是,由于現(xiàn)代CPU是64位的,通常具有64位特定的性能改進(jìn),并且RAM的價(jià)格便宜,這使得64位JVM無(wú)疑是贏家。
使用64位JVM。
減載
還是一般性建議,但對(duì)Java很重要。 為避免 GC崩潰或任務(wù)繁重而導(dǎo)致過(guò)載 ,應(yīng)用程序應(yīng)積極減少負(fù)載。 也就是說(shuō),超出某個(gè)閾值,應(yīng)用程序應(yīng)拒絕新請(qǐng)求。 盡早拒絕某些請(qǐng)求似乎很糟糕,但是比允許應(yīng)用程序變得不可恢復(fù)并導(dǎo)致所有請(qǐng)求失敗都更好。 有許多避免過(guò)載的方法,但是常見(jiàn)的方法是確保隊(duì)列有界,并且線程池的大小正確 。 此外,出站請(qǐng)求應(yīng)該有適當(dāng)?shù)慕刂谷掌?,以確保緩慢的后端不會(huì)對(duì)您的應(yīng)用程序造成問(wèn)題。
盡可能多地處理請(qǐng)求,而不再處理。
結(jié)論
希望本文使您考慮一下Java生產(chǎn)環(huán)境。 盡管不是說(shuō)明性的,但我們重點(diǎn)介紹了一些重點(diǎn)領(lǐng)域。 整個(gè)鏈接都應(yīng)指導(dǎo)您正確的方向。
如果您有任何疑問(wèn)或意見(jiàn),請(qǐng)通過(guò)@TheBramp與我聯(lián)系,或訪問(wèn)我的網(wǎng)站和博客bramp.net了解更多文章。
翻譯自: https://www.javacodegeeks.com/2017/12/running-java-production-sres-perspective.html
總結(jié)
以上是生活随笔為你收集整理的在生产中运行Java:SRE的观点的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linuxbind是什么(linux -
- 下一篇: 医疗器械许可备案系统查询(医疗器械许可备