前言
網(wǎng)上看到一本關(guān)于微服務(wù)反模式的電子書,看后感覺內(nèi)容非常棒,于是我決定分階段翻譯成中文書,翻譯的目的也是想幫助想深入了解微服務(wù)的朋友,由于英文水平有限,如有翻譯不對之處希望多留言指正。
書籍英文目錄如下
書籍中文目錄如下:
1、數(shù)據(jù)驅(qū)動的遷移反模式
1.1、太多的數(shù)據(jù)遷移
1.2、功能分割優(yōu)先,數(shù)據(jù)遷移最后
2、超時反模式
2.1、使用超時
2.2、使用斷路器模式
3、共享反模式
3.1、過多依賴
3.2、共享代碼的技術(shù)
4.到達報告反模式
4.1、微服務(wù)報告的問題
4.2、Asynchronous Event Pushing
5、沙粒陷阱
5.1、分析服務(wù)的范圍和功能
5.2、分析數(shù)據(jù)庫事務(wù)
5.3、分析服務(wù)編排
6、無因的開發(fā)者陷阱
7、隨大流陷阱
8、其它架構(gòu)模式
9、靜態(tài)契約陷阱
10、通信協(xié)議使用的陷阱
11、REST陷阱
一、數(shù)據(jù)驅(qū)動的遷移反模式
微服務(wù)會創(chuàng)建大量小的、分布式的、單一用途的服務(wù),每個服務(wù)擁有自己的數(shù)據(jù)。這種服務(wù)和數(shù)據(jù)耦合支持一個有界的上下文和一個無共享數(shù)據(jù)的架構(gòu),其中,每個服務(wù)及其對應(yīng)的數(shù)據(jù)是獨立一塊,完全獨立于所有其他服務(wù)。服務(wù)只暴露了一個明確的接口(服務(wù)契約)。有界的上下文可以允許開發(fā)者以最小的依賴快速輕松地開發(fā),測試和部署。
采用數(shù)據(jù)驅(qū)動遷移反模式主要發(fā)生在當(dāng)你從一個單體應(yīng)用向微服務(wù)架構(gòu)做遷移的時候。我們之所以稱之為反模式主要原因是,剛開始我們覺得創(chuàng)建微服務(wù)是一個不錯的主意,服務(wù)和相應(yīng)的數(shù)據(jù)都獨立成微服務(wù),但這可能會將你帶向一個錯誤的道路上,導(dǎo)致高風(fēng)險、過剩成本和額外的遷移工作。
單體應(yīng)用遷移到微服務(wù)架構(gòu)有兩個主要目標(biāo):
- 第一個目標(biāo)是單體應(yīng)用程序的功能分割成小的,單一用途的服務(wù)。
- 第二個目標(biāo)是單體應(yīng)用的數(shù)據(jù)遷移到每個服務(wù)自己獨占的小數(shù)據(jù)庫(或獨立的服務(wù))。
下圖展示了一個典型的遷移,看起來像服務(wù)代碼和相應(yīng)的數(shù)據(jù)同時進行遷移。
上圖中有三個服務(wù)是從單體應(yīng)用中劃分而來,并且還劃分獨立的三個數(shù)據(jù)庫,這是一個自然演變的過程,因為在每個服務(wù)和數(shù)據(jù)庫之間都使用了最為關(guān)鍵的限界上下文,然而我們遇到的問題也正是基于這一過程將帶領(lǐng)我們進入數(shù)據(jù)遷移的反模式。
1.1 太多的數(shù)據(jù)遷移
這種遷移路徑的主要問題是,我們很難在一次就能夠劃分清楚每個服務(wù)的粒度,從一個更粗粒度的服務(wù)開始著手,一步步的進行細化工作,并且要多了解相關(guān)業(yè)務(wù)知識,不斷的對服務(wù)的粒度進行調(diào)整,我們來看圖1-1發(fā)現(xiàn)最左邊的服務(wù)粒度太粗了,需要再拆分成二個小的服務(wù),或者你發(fā)現(xiàn)左邊的二個服務(wù)粒度劃分的又太細了,需要進行合并。而數(shù)據(jù)遷移要比源代碼遷移更復(fù)雜,更容易出錯,我們最好只為數(shù)據(jù)進行一次遷移工作,因為數(shù)據(jù)遷移是一個高風(fēng)險的工作。
我們的微服務(wù)劃分也就是應(yīng)用代碼的遷移和數(shù)據(jù)的遷移。如圖1-2所示。
1.2 功能分割優(yōu)先,數(shù)據(jù)遷移最后
此模式主要采用的是一種避免的手段,以遷移服務(wù)的功能為第一,同時也需要注意服務(wù)和數(shù)據(jù)之間的限界上下文。我們可以通過合并與拆分的手段對服務(wù)進行調(diào)整直到滿意為止,這時候就可以遷移數(shù)據(jù)了。
如圖1-3所示,左邊所有三個服務(wù)都已經(jīng)進行了遷移和拆分,但是所有服務(wù)仍然使用的是同一個數(shù)據(jù)庫,如果這是一個臨時中間方案還可以作為一個選擇,這時候我們就需要更多的了解服務(wù)如何使用,以及接受什么類型的請求數(shù)據(jù)等。
在圖1-3中,我們要注意最左邊的服務(wù)是如何發(fā)現(xiàn)粒度太粗而拆分成二個服務(wù)的。服務(wù)粒度最終確定完成之后,下一步就開始遷移數(shù)據(jù)了,采用這種方式可以避免重復(fù)的數(shù)據(jù)遷移。
二、超時反模式
微服務(wù)是一種分布式的架構(gòu),它所有的組件(也就是服務(wù))會被部署為單獨的應(yīng)用程序,并通過某種遠程訪問協(xié)議進行通訊。分布式應(yīng)用的挑戰(zhàn)之一就是如何管理遠程服務(wù)的可用性和它們的響應(yīng)。雖然服務(wù)可用性和服務(wù)響應(yīng)都涉及到服務(wù)的通信,但它們是兩個完全不同的東西。服務(wù)可用性是服務(wù)消費者連接服務(wù)并能夠發(fā)送請求的能力,服務(wù)響應(yīng)則關(guān)注服務(wù)的響應(yīng)時間。
如圖2-1的所示,如果此時服務(wù)消費者無法連接到服務(wù)提供者的時候,通過會在毫秒級的時間里得到通知和反饋,這時候服務(wù)消費者可以選擇是直接返回錯誤信息還是進行重試,但是如果服務(wù)提供者接收了請求卻不進行響應(yīng)該怎么辦,在這種情況下服務(wù)消費者可以選擇無限期等待或者設(shè)置超時時間,使用超時時間看起來是個好辦法,但是它會導(dǎo)致超時反模式。
2.1 使用超時
你可能感覺非常困惑,難道設(shè)置一個超時時間不是一件好事嗎?在大部分的情況下超時時間的錯誤設(shè)置都會帶來問題。比如當(dāng)你上網(wǎng)購物的時候,你提交了訂單,服務(wù)一直在處理沒有返回,你在超時的時候再提交訂單,顯然服務(wù)器需要更復(fù)雜的邏輯來處理重復(fù)提交訂單的問題。
那么超時時間設(shè)置多少合適呢?
- 第一種是基于數(shù)據(jù)庫的超時來計算服務(wù)的超時時間。
- 第二種是計算負載下最長的處理時間,把它乘以2作為超時時間。
在圖2-2中,通常的情況下平均響應(yīng)時間是2秒,在高并發(fā)的情況下最長時間是5秒,因為可以使用加倍技術(shù)服務(wù)的超時時間設(shè)置為10秒。
圖2-2的解決方案似乎看起來很完美,它使每一個服務(wù)消費者必須等待10秒,其實只是為了判斷服務(wù)沒有響應(yīng)。在大多數(shù)情況下,用戶在等待提交按鈕或放棄和關(guān)閉屏幕之前不會等待超過2到3秒。那就必須要有更好的辦法來解決。
2.2 使用斷路器模式
與上面超時的方法相比,使用斷路器的方式更為穩(wěn)妥,這種設(shè)計模式就像家里的電器的保險絲一樣,當(dāng)負載過大,或者電路發(fā)生故障或異常時,電流會不斷升高,為防止升高的電流有可能損壞電路中的某些重要器件或貴重器件,燒毀電路甚至造成火災(zāi)。保險絲會在電流異常升高到一定的高度和熱度的時候,自身熔斷切斷電流,從而起到保護電路安全運行的作用。
圖2-3說明了斷路器模式是如何工作的,當(dāng)服務(wù)保持響應(yīng)時,斷路器將關(guān)閉,允許通過請求。如果遠程服務(wù)突然變得不能響應(yīng),斷路器就會打開,從而阻止請求通過,直到服務(wù)再次響應(yīng)。當(dāng)然這并不像你家中的保險絲,斷路器本身可以持續(xù)監(jiān)測服務(wù)。
斷路器模式相比設(shè)置超時的優(yōu)點是,使用者可以立即知道服務(wù)已變得不響應(yīng),而不必等待超時,使用者將在毫秒內(nèi)服務(wù)不響應(yīng),而不是等待10秒獲得相同的信息。
另外斷路器可以通過幾種方式進行監(jiān)控,最簡單的方法是對遠程服務(wù)進行簡單的心跳檢查,這種方式只是告訴斷路器服務(wù)是活的,但是要想獲取服務(wù)存活的詳細信息,就需要定期(比如10秒)獲取一次服務(wù)的詳細信息,還有一種方式是實時用戶監(jiān)控,這種方式可以動態(tài)調(diào)整,一旦達到閾值,斷路器可以進入半開放狀態(tài),可以設(shè)置一定數(shù)量的請求是通過(說1的10)。
三、共享反模式
微服務(wù)是一種無共享的架構(gòu),我更傾向于叫它為“盡量不共享”模式(share-as-little-as-possible), 因為總有一些代碼會在微服務(wù)之間共享。比如不提供一個身份驗證的微服務(wù),而是將身份驗證的代碼打包成一個jar文件:security.jar,其它服務(wù)都能使用。如果安全檢查是服務(wù)級別的功能,每個服務(wù)接收到請求都會檢查安全性,這種方式可以很好的提高性能。
然后如果太過頻繁的使用最終會出現(xiàn)依賴噩夢,如圖3-1所示,其中每個服務(wù)都依賴于多個自定義共享庫。
這種共享級別不僅破壞了每個服務(wù)的限界上下文,而且還引入了幾個問題,包括整體可靠性、更改控制、可測試性和部署能力。
3.1 過多依賴
在面向?qū)ο蟮能浖_發(fā)過程中,經(jīng)常會遇到共享的問題,特別是從單一分層結(jié)構(gòu)遷移到微服務(wù)結(jié)構(gòu)時,圖3-2展示抽象類和共享,它們最終在多數(shù)單塊分層體系結(jié)構(gòu)中共享。
創(chuàng)建抽象類和接口是面向?qū)ο缶幊痰淖钪匾龇ǎ俏覀內(nèi)绾蝸硖幚頂?shù)百個服務(wù)共享的代碼?
微服務(wù)架構(gòu)的主要目標(biāo)就是共享要盡可能的少,這有助于維護服務(wù)的限界上下文,使我們能夠快速的測試和布署。服務(wù)之間依賴越強,服務(wù)隔離也就越困難,因此也就越難單獨進行測試和布署。
3.2 共享代碼的技術(shù)
要避免這個反模式的最好辦法就是代碼不共享,但是實際工作中總會有一些代碼需要進行共享,那這些共享代碼應(yīng)該放到哪里呢?
圖3-3給了四個最基本的技術(shù):
- 共享項目
- 共享庫
- 復(fù)制
- 服務(wù)合并
四、到達報告反模式
有四種方式可以處理微服務(wù)架構(gòu)中的報告。
- database pull model
- HTTP pull model
- batch pull model
- event-based push model
前三種模式是從服務(wù)的數(shù)據(jù)庫中拉取數(shù)據(jù),所以這個反模式就叫"rearch-in reporting"。既然前三種會出現(xiàn)這中反模式,我們就先看看為什么它們會帶來麻煩。
4.1 微服務(wù)報告的問題
主要是二個方面的問題:
- 如何及時獲取最新數(shù)據(jù)
- 保持服務(wù)與數(shù)據(jù)之間的限界上下文
在微服務(wù)架構(gòu)體系中第一種是使用數(shù)據(jù)庫拉取模型,使用者直接從服務(wù)的數(shù)據(jù)庫拉取數(shù)據(jù),如圖4-1所示:
其實獲取數(shù)據(jù)最快、最容易的方法是直接訪問數(shù)據(jù)。雖然這在以前看似乎是個好主意,但它導(dǎo)致了服務(wù)之間的明顯依賴關(guān)系。而上圖會帶來數(shù)據(jù)庫的非獨立性。
避免數(shù)據(jù)的耦合的另一種技術(shù)稱為HTTP拉取模型。使用此模型不需要直接訪問每個服務(wù)的數(shù)據(jù)庫,使用者只需要對每個服務(wù)發(fā)出一個REST HTTP調(diào)用就可以訪問其數(shù)據(jù)。如圖4-2所示。
這種方式的優(yōu)點是根據(jù)限界上下文劃分出了不同服務(wù),但是這種方式又太慢,無法滿足復(fù)雜的以及數(shù)據(jù)量較大數(shù)據(jù)獲取需求。
第三種是批量拉取模式,這種方式是獨立出一個報表數(shù)據(jù)庫或者數(shù)據(jù)倉庫,通過批處理作業(yè)將不同服務(wù)數(shù)據(jù)庫的數(shù)據(jù)拉取這個新獨立的數(shù)據(jù)庫中,如圖4-3所示。
這種模型的問題在于依然是強依賴數(shù)據(jù)庫,如果拉取服務(wù)的數(shù)據(jù)庫進行了更新,那么這個批量數(shù)據(jù)拉取過程也必將修改。
最后一種是異步事件模型,也是推薦使用的模型,如圖4-4所示
五、沙粒陷阱
架構(gòu)師和開發(fā)人員在采用微服務(wù)架構(gòu)的時候最大的挑戰(zhàn)之一就是服務(wù)粒度的問題。微服務(wù)的服務(wù)粒度多大合適?服務(wù)粒度至關(guān)重要,它會影響應(yīng)用的性能、健壯性、可靠性、可測性、設(shè)置發(fā)布模型。
當(dāng)服務(wù)的粒度太小的時候就會遇到沙粒陷阱。微服務(wù)的微并不意味著服務(wù)越小越好,但是多小是小?
這種陷阱的主要原因之一是開發(fā)人員常常將服務(wù)與類混淆,往往一個類就是一個服務(wù),在這種情況下會很容易遇到沙粒陷阱。
服務(wù)應(yīng)該被看成是一個服務(wù)組件,服務(wù)組件應(yīng)該有一個清晰簡明的角色和責(zé)任定義,并有一組明確的操作。由開發(fā)人員決定服務(wù)組件應(yīng)該如何實現(xiàn)以及服務(wù)需要多少個實現(xiàn)類。
如圖5-1所示,服務(wù)組件是通過一個或多個模塊的實現(xiàn)(比如,java類)。模型和服務(wù)組件如果是一對一的關(guān)系會使服務(wù)的粒度過細而后期難以維護,而通過一個類實現(xiàn)的服務(wù)往往類太大,承擔(dān)太多的責(zé)任,也使它們難以維護和測試。
當(dāng)然微服務(wù)的粒度并不是靠服務(wù)實現(xiàn)的類的數(shù)量所決定的,有些服務(wù)很簡單,只需一個簡單的類就可以實現(xiàn),而有些確需要更多的類。既然類的數(shù)量不能用來決定微服務(wù)的粒度,那么用什么標(biāo)準來衡量微服務(wù)的粒度是合適的呢?
主要有三種方式:
- 服務(wù)的范圍(scope)和功能(functionality)
- 數(shù)據(jù)庫事務(wù)的需求
- 服務(wù)編排的級別。
5.1 分析服務(wù)的范圍和功能
確定服務(wù)粒度級別是否正確的第一種方法是分析服務(wù)的范圍和功能。服務(wù)是做什么的?它的操作是什么?
比如一個顧客服務(wù)(customer service)有下面的操作:
- add_customer
- update_customer
- get_customer
- notify_customer
- record_customer_comments
- get_customer_comments
在這個例子中前三個操作是相關(guān)的,它們都是用來管理和維護顧客信息的,但是后面三個并不是和CRUD操作相關(guān)的。在分析這個服務(wù)的完整性的時候,我們就比較清晰了,這個服務(wù)可以被分成三個服務(wù):顧客信息服務(wù)、顧客通知服務(wù)和顧客評論服務(wù)。
圖5-1 正是一種由粗粒度服務(wù)向細粒度服務(wù)逐步演進的過程。
Sam Newman提供了一個很好的可操作的方法,開始不妨將服務(wù)劃分成粗粒度的服務(wù),隨著對服務(wù)了解更多,再進一步劃分成更小粒度的服務(wù)。
5.2 分析數(shù)據(jù)庫事務(wù)
數(shù)據(jù)庫事務(wù)更正式的叫做 ACID 事務(wù) (atomicity, consistency, isolation, and durability)。ACID事務(wù)封裝多個數(shù)據(jù)庫更新為一個工作單元,工作單元要不整體完成,要不就出現(xiàn)錯誤而回滾。
因為微服務(wù)架構(gòu)中服務(wù)是分布式的獨立的應(yīng)用,再兩個或者多個服務(wù)之間維護 ACID 事務(wù)就極度困難,所以微服務(wù)架構(gòu)中經(jīng)常會依賴 BASE (basic availability, soft state, and eventual consistency)。盡管如此,你還是再特定的服務(wù)中要使用 ACID 事務(wù)。當(dāng)你需要在 ACID vs. BASE 事務(wù)中做艱難的決定的時候,可能你的服務(wù)劃分的就太細了。
當(dāng)發(fā)現(xiàn)不能使用最終一致性時,你通常就會把服務(wù)從細粒度調(diào)整為粗粒度的服務(wù),如圖5-2所示。
5.3 分析服務(wù)編排
第三個衡量方式是分析服務(wù)編排。服務(wù)編排是指服務(wù)之間的通訊,通常也指內(nèi)部服務(wù)通訊。
遠程調(diào)用服務(wù)是需要花時間的,它會降低應(yīng)用整體的性能。再者,它也會影響服務(wù)的健壯性和可靠性。
如果你發(fā)現(xiàn)完成一個邏輯請求需要調(diào)用太多的服務(wù)時,服務(wù)的劃分可能粒度就太小了,對于單個業(yè)務(wù)請求,你調(diào)用的遠程調(diào)用越多,其中一個遠程調(diào)用失敗或超時的可能性就越大。
如果你發(fā)現(xiàn)需要與太多的服務(wù)進行通信以完成單個業(yè)務(wù)請求,那么你的的服務(wù)可能粒度過細了。在分析服務(wù)編排水平,你通常會從細粒度的服務(wù)遷移到更粗,如圖5-4所示。
通過整合服務(wù)、合并到更粗粒度可以提升應(yīng)用的整體性能,提高應(yīng)用的健壯性和可靠性。你還可以移除服務(wù)之間的依賴,可以更好的控制、測試和發(fā)布。
當(dāng)然你可能會說調(diào)用多個服務(wù)可以并行的執(zhí)行,提高整體應(yīng)用的的響應(yīng)時間,比如 reactive 架構(gòu)的異步編程方式, 其實關(guān)鍵還是要權(quán)衡利弊, 確保對用戶的及時響應(yīng)以及系統(tǒng)整體的可靠性。