天天看點

減少使用Java應用伺服器,迎接Docker容器

本文講的是<b>減少使用Java應用伺服器,迎接Docker容器</b>,【編者的話】随着Docker的發展,越來越多的應用開發者開始使用Docker。James Strachan寫了一篇有關Java開發者如何使用Docker進行輕量級快速開發的文章。他告訴我們,使用Docker和服務發現的機制,可以有效減輕Java運維人員的負擔,進行項目的快速啟動和持續疊代。

多年來,Java生态系統一直在使用應用伺服器。Java應用伺服器(如Servlet Engine、JEE或OSGi)是一個可以作為最小部署單元(如jar/war/ear/bundle等)進行部署和解除安裝Java代碼的JVM(Java虛拟機)程序。是以一個JVM程序可以在運作的過程中更換運作在其上的代碼。通常Java應用伺服器提供存放檔案的目錄或者REST/JMX 接口來修改正在運作的部署單元(Java代碼)。

由于記憶體資源在過去是相當寶貴的,是以把所有的Java代碼放到同一個JVM中去運作來減少多個程序帶來的記憶體碎片具有重要的意義。

多年來,在Java生産環境中,通常沒有人真正在運作着的JVM中解除安裝Java代碼,因為這樣做很容易造成記憶體洩漏(線程、記憶體、資料庫連結、socket、正在運作的代碼等導緻)。是以在生産環境中更新應用的較好做法是并行地在一個新的應用伺服器中啟動應用程式;把流量從舊的應用執行個體遷移到新的應用執行個體上,當舊的應用執行個體結束正在處理的請求時,就可以被停止。

從概念上說是解除安裝了舊的程式,部署了新的程式;但是實際上是啟動了一個新的程序,并把流量遷移到新的程序上,然後結束那個舊程序。

目前,有向微服務發展的趨勢,每個程序做好一件事。多年來,使用應用伺服器的最佳實踐方式,一直都是在每一個JVM中部署盡量少的部署單元。假如你把所有的服務(部署單元)部署到同一個JVM中;如果要更新這些服務中的一個,你就要關閉這個JVM程序,這就會影響到其它的服務。是以把每個應用單獨部署在不同的JVM程序中更安全和靈活,這樣在任何時候更新一個服務都不會影響到其他的服務。

多個獨立的程序比一個龐大的程序更容易監控,也更容易了解哪個服務使用了多少記憶體、網絡、硬碟和CPU等。

Docker容器提供了一種理想的方式來打包應用,使得應用在Linux機器上部署更加友善;對不同的操作環境和不同的程式都可以使用同一個Docker鏡像而不需要改變;容器之間彼此隔離,并且通過cgroups對IO、記憶體、CPU等的用量進行限制。所有在Linux上可以使用的技術(Java、python、ruby、nodejs、golang等)都可以在Docker容器中很好的運作。

Docker容器最大的優點之一就是你可以以重複的方式在任何機器上同時啟動多個執行個體,因為這些執行個體都是基于同一個不變的、可重複使用的鏡像。每個容器執行個體都可以把自己的持久狀态挂在在卷上,但是它們的代碼(甚至配置)都來自同一個不變的鏡像。

是以在Docker上使用Java應用伺服器的方式是為應用伺服器和你想在生産環境中運作的部署單元建立一個鏡像。

在更新服務的時候不再需要在webapps/deploy目錄下删除掉一個WAR包或者調用 REST/JMX接口,或者任何其它方式,你隻需要建立一個包含新的部署單元的鏡像,并且運作這個鏡像。

此外,Java應用伺服器不再需要在運作時部署和解除安裝新的代碼;不再需要監控部署目錄的變化或者監聽來自REST/JMX接口的更改部署的請求;隻需要在啟動的時候啟動鏡像中的代碼。

是以在Docker的世界中,Java應用伺服器的理念(可以部署和解除安裝程式的動态JVM)正在逐漸消亡。

自采用應用伺服器以後,在Java生态環境中,應用被建立成一個不可變的二進制部署單元(jars、wars、ears、bundles等),釋出一次就可以在不同的環境中使用。為了做到在不同的環境中運作,我們通常通過應用服務來查找資源(例如,在JEE環境下使用JNDI查找)比如查找資料庫的位置或者消息代理。是以就會有單獨的配置好的應用伺服器叢集來部署你的程式(假設應用伺服器都配置正确)。

盡管在不同的作業系統,Java版本,應用伺服器版本或者不比對的配置等不同環境下容易混亂,在初步階段程式可能還正常運作,但是如果不夠仔細的話,生産環境下可能會運作出錯。

而采用Docker的方法,就是把鏡像不變的理念延伸到作業系統和應用伺服器上;是以根據作業系統、java環境,應用伺服器和部署單元制定的同一個二進制鏡像可以在每一個特定環境下運作。是以在一個特定環境下不存在應用伺服器配置錯誤的問題,因為同一個二進制鏡像可以在所有環境下運作。

是以,這就意味着Java應用伺服器沒用了嗎?在Docker的世界裡,确實再也沒有必要在生産環境中運作着的Java程序中熱部署Java代碼了。但是在開發過程中,有能力在運作的執行個體中熱部署一份代碼依舊非常有用。(盡管公平的說,你可以使用像JRebel這樣的工具在Java應用做到同樣的事情,大多數使用IDE調試的使用者就用這種方法)

是以我想說,Java應用伺服器漸漸變得更像燒錄到固定鏡像中的一個架構,然後在外部雲中進行管理(比如通過Kubernetes)。雲(如Kubernetes和Docker)在許多方面接管了很多Java應用伺服器原先做的功能,并且新鏡像的滾動更新對所有技術來說都是需要的(包括java/golang/nodejs/python/ruby等等)。

盡管Java使用者仍然想要Java應用伺服器提供的一些服務,如servlet引擎、依賴代碼注入、事務處理、消息處理等等。但是你再也無需動态的在一個運作着的Java虛拟機中清理原先部署上去的代碼了,這樣你就可以輕易的在Java應用中植入一個servlet引擎。像Spring Boot這樣的方法向你展示了如何隻通過依賴代碼注入和一個扁平化的類載入器,就足以勝任大多數應用伺服器的功能。

作為一個開發者,在用Java應用伺服器工作時遇到最大問題之一就在于載入Java類時的複雜性,我相信在這一點上我們都讨厭Java的類載入器問題。

盡管你可以通過使用BOM檔案在項目中導入一個maven建構的依賴關系來修複這些問題,但是為JEE服務開發者們屏蔽jar包的具體實作依然有一定的價值,你無需再搞清複雜的類載入器的樹關系或者圖關系。就算類路徑關系簡單,你還有可能面臨版本沖突問題。是以如果有辦法隔離類載入器會非常有用。不過有時候使用一個jar包的不同版本也意味着編碼上可能有些問題,是不是意外着是時候把代碼重構一下,變成兩個獨立的服務,這樣就可以有一個簡潔漂亮扁平的類載入器?

如果一個Java應用伺服器程序現在隻啟動了一個靜态已知的Java代碼集合,應用伺服器的想法會變成一個幫助你進行代碼注入以及包含你所需子產品服務的方法,這就聽起來更像是一個架構而非我們原本意外的一個Java 應用伺服器。

許多Java開發者學會了如何使用應用伺服器,并且在Docker的世界中仍會繼續使用,這一點很好。但是與此同時我也看到,他們對此的使用真在消減,因為許多應用伺服器本來在過去幫我們完成的事情,現在Docker、Kubernetes及相關架構可以用一種更簡單、更高效的方式幫我們完成。

Docker和雲給我們帶來的一個巨大的好處就是,開發者可以選擇他們想要使用的技術,他們可以為合适的工作選擇适當的工具,并且可以把他們的技術用同樣的方法進行管理和提高給使用者,無論使用的是何種語言何種架構。你可以在最初使用你知道的技術,随着時代的變化遷移到更輕量級的替代中。

我越來越多的看見Java使用者選擇像Camel Boot、CDI、Dropwizard、Vertx或者Spring Boot 這些更輕量級的架構,并且随着時間越來越少使用Java應用伺服器。盡管我們依然需要使用依賴注入和架構。

原文釋出時間為:2015-03-20 

本文作者:wonderflow

本文來自雲栖社群合作夥伴DockerOne,了解相關資訊可以關注DockerOne。

原文标題:減少使用Java應用伺服器,迎接Docker容器