從前世看今生,從JavaEE到微服務

我有一個習慣,接觸到新概念、新技術出現後,就會探究他的前世今生、來龍去脈,正所謂“太陽底下沒有新鮮事”,喜歡從對比中找到價值點,不如此就覺得理解不透徹,就覺得少了點什麼。微服務的概念出現後,由於又有了服務這個詞,大家往往和麪向服務架構做對比,類似文章即便不是汗牛充棟,也可算作車載斗量。

但由於SOA 架構是企業架構層面的一種方法,視角比較宏觀(例如建設銀行新一代系統就是採用SOA架構),再者SOA涉及的標準規範例如XML、SOAP、WSDL、UDDI、SCA/SDO等又偏重在互聯互通的協議上,這種對比總覺得不對等,二義性很強。

我喜歡拿J2EE和微服務做對比,因爲J2EE有一系列明確的規範,可以互相印證。J2EE出現在1998年(參見https://zh.wikipedia.org/wiki/Java_EE,維基百科的描寫比百度百科準確很多),2006年以後改名爲JavaEE,是Java企業版的意思,目前是JavaEE7,明年應該有JavaEE8,可惜現在關注的越來越少了。JavaEE既然是企業版(Enterprise這個詞翻譯成企業不一定合適,我覺得在英文裏是一個組織的意思,政府機構也是Enterprise,是不是翻譯成“單位”,哈哈),就必然支持分佈式系統,很多微服務架構的要求,在JavaEE規範中都有涉及,參考JavaEE規範可以更容易的理解如何實現微服務架構。

讓我們看一下JavaEE規範,和微服務架構做一個比較:



(爲了寫這篇文章,我第一次翻出了JavaEE 7規範,看着這些名詞,唏噓不已,不免感嘆又一個時代過去了。)JavaEE規範涉及的內容很多,這裏只對EJB、JNDI、Servlet、JSP、JMS、JTA等規範做一個對比說明。


1、 從EJB這個失敗的規範理解微服務的後端服務

說起JavaEE規範,要先從EJB(Enterprise Java Bean),他是一種用Java實現後端服務的規範。本來EJB是JavaEE中最重要的規範,但EJB出現後,人們一直詬病他過於複雜的使用方式,在Spring出現後,大家其實拋棄了EJB,雖然他自身做了很多改革,以至於EJB 3.0 後和Spring非常類似,然並卵… … 其實,EJB的設想還是很好的,他把後端服務分爲會話Bean(Session Beans)、實體Bean(Entity Beans)、消息驅動Bean(Message Driven Beans)三種模式,前者又分爲 無狀態會話Bean(Stateless Session Beans)、有狀態會話Bean(Stateful Session Beans),最初EJB完全是使用遠程調用的,後來由於性能的原因,又加上了本地模式,上述四種EJB 都可以採用本地調用。結合微服務架構,我們來回顧一下:

首先服務應該被分爲本地和遠程兩種方式,我一向反對這兩種服務處理的透明化,原因是這兩種調用在應用開發上差別太大,例如遠程調用應該採用異步回調模式,設置明確的超時時間,事務處理不能依賴數據庫事務,數據傳遞需要序列化,需要傳遞上下文等等,而本地調用可以簡單的多。EJB開始時把所有的東西都做成遠程模式,後來由試圖兩者都支持,結果本來複雜的事情沒簡單下來,簡單的事情反而複雜了,所以我在微服務架構中,把本地和遠程服務顯示分開,採用不同的API進行調用,對於遠程服務需要採用異步模式調用,配置超時時間、數據一致性聲明、通訊報文定義等等,不去幻想用一種透明方式進行動態切換,其實把本地服務變成遠程服務的工作量是遠大於這幾行代碼開發的,所以本地/遠程調用透明化只是一個看起來很美,這一點上EJB是失敗的。

其次,EJB把服務分成無狀態和有狀態兩種,無狀態服務沒什麼好說的,大家都在做無狀態化,以便有利於橫向伸縮和彈性。無狀態雖好,但是業務其實是有狀態的,但Servlet規範中有Session,常見的客戶登錄信息等狀態都維護在Session中,再者還有很多業務狀態也可以在客戶端維護,例如翻頁時的計數器,在客戶端保存,每次提交到服務端,這樣有狀態服務使用的場景在業務上反而少了。但移動設備出現後,多屏融合的需求讓我們無法在客戶端維護狀態了,例如在PC上做一個操作,在手機上做下一步,就只有服務端維護狀態才行。服務端維護狀態也不是說一定要用有狀態服務,因爲這些信息可以維護在數據庫中,即使考慮性能因素,也可以維護在集中緩存中,服務還是無狀態的。上面說了很多,是說明爲什麼有狀態服務使用比較少,但物聯網出現後,有狀態服務重新有擡頭的趨勢,例如在讀取設備信息時,必須在服務端維護狀態,但由於數據量比較大,集中在緩存的方式導致緩存過大,不容易維護,於是就要分而治之,有狀態服務就是一個好的選擇了。其實,有狀態服務經常默默的爲我們服務,例如客戶端獲得一個數據庫連接,以後對這個數據庫連接做操作時,數據庫本身就是維護了一系列的有狀態服務,服務狀態包括登錄信息、緩存數據等上下文信息,每次根據客戶端的標識找到這個服務,提供服務。在微服務架構的實現中,需要考慮有狀態的模式,可以參考EJB的設計,把遠程服務分爲無狀態和有狀態兩種。

實體Bean(Entity Beans)是含有持久化狀態的分佈式對象。這個持久化狀態的管理既可以交給Bean自身(Bean-Managed Persistence,BMP),也可以託付於外部機制(Container-Managed Persistence,CMP)。如果說會話Bean出現的早期還有很多應用,實體Bean一出現就讓人感到沒法用,分佈式對象這玩意,還是太複雜了。我也僅僅是做過Demo而已,從來沒有實際的應用,不過在我看來,IBatis和Hibernate應該是BMP和CMP最好的實踐了,而IBatis和Hibernate都不是面向分佈式應用的,他們都迎合了當時巨石應用的架構模式,以至於EJB 3.0 中和Hibernate已經非常類似了。在微服務架構下,數據必然是分佈式的,而數據的存儲方式也從關係數據庫拓展到緩存、NoSQL、圖等數據存儲方式,實體Bean實在是分佈式數據的早期探索之一,只不過這個嘗試失敗了。

消息驅動Bean(Message Driven Beans)是基於JMS事件驅動方式觸發後端服務的模式,無非是在EJB之上加一個事件驅動的外殼。微服務架構下,也支持事件驅動的方式,以後再詳細論述。

EJB規範的目的在於爲企業及應用開發人員實現後臺業務提供一個標準方式,自動處理了諸如數據持久化、事務處理、併發控制、基於JMS的事件驅動、基於JNDI的名字和空間管理、基於JCE和JAAS的安全管理、應用服務器端的軟件組件部署、使用RMI-IIOP協議的遠程過程調用、將業務方法暴露爲Web服務、以及如何將EJB部署至EJB容器當中,雖然這是一個不成功的嘗試,但這些都是微服務架構需要考慮的問題。


2、JNDI  Java版的服務發現

Java命名和目錄接口(Java Naming and Directory Interface),是Java的一個目錄服務API,它提供一個目錄系統,並將服務名稱與對象關聯起來,從而使得開發人員在開發過程中可以使用名稱來訪問對象,這個規範是JavaSE的一部分,而JavaEE建立在JavaSE之上,JNDI也是JavaEE一個重要的基石。上面的解釋比較拗口,其實解決的是服務註冊、發現和配置集中管理問題。看看JNDI的示例:


示例:服務查找

Context ctx = ...
Object obj =ctx.lookup("/MyDataSource");


示例:註冊監聽器,可用於監聽配置的改變

NamespaceChangeListener listener = ...;
src.addNamingListener("x",SUBTREE_SCOPE, listener);


示例:監聽器被觸發,獲得變更前狀態

evt.getEventContext()== src;
evt.getOldBinding().getName().equals("x/y")


JNDI規範雖好,但我們最常用就是lookup一個DataSource,之所以這樣我認爲有幾個原因:a) JavaEE雖然號稱是面向分佈式應用,但實際情況絕大多數不是分佈式應用,對服務註冊、發現的需求很低;b)每個應用服務器的實現差距很大,尤其是命名方式和服務綁定(bind)上,以至於後來bind的接口主要用於應用服務器內部實現了,一個難以做服務註冊的服務發現自然難有太大的用處;c)從命名服務中查找得到的是對象,對象一般都需要實例化的,如果是遠程對象又涉及到方法的調用問題,加大了複雜度。d)非Java的環境無法使用。這幾個問題其實也是其他規範難以推廣的原因,所以在微服務架構下,Zookeeper、etcd解決的就是上述問題,重讀JNDI對規範化服務發現、註冊、配置集中管理的接口有很大的幫助。


3、Servlet:Java API網關

Servlet是用Java編寫服務端程序的接口,在J2EE出現之前,服務端程序一般都是用CGI實現的, Servlet的出現讓Java的服務端程序有了統一的模式。早期我們會把每一個響應請求的類都實現Servlet的接口,後來在很多框架中都把Servlet做成統一的入口,由框架進行分發,編程的時候就看不到Servlet了。Servlet一直在被廣泛使用,在微服務架構中也可以被做爲前端接入存在。


4、JSP:成功的服務端模板技術

JSP是一種把Java語言嵌入到靜態頁面,動態生成HTML或其他格式Web網頁的技術標準,他解決了Servlet 生成Web網頁比較麻煩的問題。JSP促進了很多框架的產生,不過在Axaj模式出現後,JSP的使用方式也發生了很大變化,前端更加趨向於客戶端的渲染,而不是在服務端生成全部Web頁面。微服務架構下前端有很多實現方式,JSP只是選擇之一。我們曾經在全國產平臺上做過測試(龍芯、麒麟等組合),由於國產芯片的計算能力不足,造成瀏覽器上的渲染速度不夠,這時候前端動態渲染的效果很不好,反倒是傳統JSP 在服務端生成Web頁面的模式體驗更佳。


5、JTA:分佈式事務的嘗試

Java事務API(Java Transaction API) 是在Java環境中,允許完成跨越多個XA資源的分佈式事務。JTA的接口比較簡單,但是實現起來卻比較複雜,事實上很少有人嘗試使用基於XA 資源的分佈式事務,JTA往往被框架(例如Spring)做爲底層的本地事務接口,實現業務邏輯的事務一致性聲明。在事務聲明方面EJB作出了很大貢獻,是他率先將Required、RequiresNew、Mandatory、NotSupported、Supports、Never這樣的事務聲明引入到Java的體系中,後來在 Spring 中被廣泛大家使用了,而JTA正是這一實現的支撐。在微服務架構中,本地事務還應該是這種方式,麻煩的是遠程服務的事務。對分佈式事務的實現方式,請參考我的同事田向陽《微服務架構下的數據一致性保證(一)》和劉相《分佈式事務:不過是在一致性、吞吐量和複雜度之間,做一個選擇》的文章,在此之上,我們也應該參考EJB或者Spring的方式,用事務聲明的方式維護數據的一致性,當然這個聲明會遠遠複雜於本地事務。


6、JMS:通過JMS 看成功的JavaEE規範

Java消息服務(JavaMessage Service)是一個Java平臺中關於面向消息中間件(MOM)的API,用於在兩個應用程序之間,或分佈式系統中發送消息,進行異步通信。應該說JMS規範的使用還是很多的,是JavaEE中比較成功的一個規範,除應用服務器之外的很多消息系統也都支持這個規範(例如kafka)。既然是一個很受歡迎的規範,對這個技術本身我沒什麼可說的,繼續保持發揚吧,我想說的是,這個規範爲什麼做的好,受歡迎。究其原因是當時人們對於消息編程的理解比面向對象編程更加深刻,面向批處理編程、面向消息編程是在面向對象編程之前的狀態,JavaEE規範的出現正是人們基於Java這樣面向對象的語言實現企業應用的過程,這也證明嚴格的面向對象方式實現企業分佈式應用並不是好的選擇,在JavaEE規範中使用比較好的JSP、Servlet、JDBC、JMS等都不是面向對象的編程模式,JSP是模板式的、Servlet是請求響應式的、JDBC是面向結果集的、JMS是面向消息的。


7、從JavaEE部署規範看Docker與微服務架構的關係

JavaEE規範中,EAR、WAR、JAR的部署模式是大家最常見的方式,按照JavaEE的設想,每一個模塊都是一個獨立的可部署單元,前端界面、後端服務都是可以獨立部署的,而應用服務器對多個模塊進行統一管理。但是,由於JavaSE天然的缺陷,應用之間難以實現隔離,而應用對第三方類庫的依賴也讓多應用的管理變得難以接受。而Docker的到來既實現了應用的隔離,也加快了應用的部署。所以,我把Docker用在應用的部署上,用於解決JavaEE沒有解決好的問題上。在我看來,Docker不是一個輕量級的虛擬機,他不應該僅僅用在替換虛擬機上,而是把他們之間的區分使用,把資源的分配、安全等問題交給虛擬機,而Docker象EAR一樣做應用的打包工具,這樣,把Docker部署在虛機上就不顯的奇怪了,誰會對EAR部署在虛機上感到奇怪呢?

最後,我要說的是JavaEE規範建立在三層/多層應用架構體系之上(如下圖左),但在數字化時代應用程序必須支持多個客戶端渠道(例如,桌面,移動,社交),並且這些前端應用程序與後端服務交付(如下圖右)。未來SOA仍然是企業架構的核心,但SOA的實現將從JavaEE的三層架構向輕量級的微服務架構演進,API是服務的接口,微服務架構則代表了提高敏捷性和可伸縮性的範例。我們提供的微服務應用平臺,其實就是實現新一代的應用服務器:將中間件微服務化,將微服務工程化。


在下圖上,我把微服務架構中與JavaEE規範對應的部分畫出來,供大家在實現微服務時做參考:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章