這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
目錄 [−]
- 資料驅動的遷移反模式
- 太多的資料移轉
- 功能分割優先,資料移轉最後
- 逾時反模式
- 使用逾時
- 使用熔斷器設計模式
- 共用反模式
- 太多依賴
- 共用代碼的技術
- 到達報告反模式
- 微服務報告的問題
- Asynchronous Event Pushing
- 沙粒陷阱
- 分析服務的範圍和功能
- 分析資料庫事務
- 分析服務編排
- 無因的開發人員陷阱
- 隨大流陷阱
- 其它架構模式
- 靜態契約陷阱
- 我們到了嗎陷阱
- REST陷阱
前幾天我寫了篇讀書筆記: 《產品級微服務的八大原則》,介紹了Uber的SRE工程師 Susan J. Fowler 的免費書: Microservices in Production,文中提出了一個微服務成功與否的唯一標準就是可用性,非常有實踐意義。但是這本書偏向於從 SRE (site reliability engineer)的視角看待微服務,對於開發工程師 (SWE, software engineer)來說,更關注的是如何正確地從單體程式重構到微服務架構,或者從頭設計微服務架構, 這篇讀書筆記主要就是介紹這方面的實踐和經驗。
Oreilly 的 的這本免費小書 Microservices AntiPatterns and Pitfalls由經驗豐富的 Mark Richards 編寫。書中將反模式(AntiPattern)定義為"起初看起來很美好,做到最後麻煩不斷的實踐模式",而將陷阱(Pitfall)定義為“起初看起來就不是一個好的設計”,書中列舉了微服務開發中幾種常見的反模式和陷阱,這些經驗非常的接地氣.他還提供了視頻教程。
2006年 SOA (service-oriented architecture) 狂熱流行,無數的公司隨著技術潮流擁抱 SOA, 不管它們是否已經完全理解了這種複雜的架構風格的長處和短處,這些公司不可避免地陷入和服務粒度、效能、資料移轉以及SOA組織改變的鬥爭中,很多公司不得不放棄了 SOA, 或者折衷的建立了一種混雜的架構風格。
曆史的悲劇又將重演。微服務是行業當前的發展趨勢。現在,微服務就像2000年中期的 SOA 一樣是技術的熱潮。很多公司都在朝著微服務架構的方式改變,以期獲得微服務架構的好處,例如便於測試,快速,易於部署,細粒度的可擴充性,模組化和敏捷性。然而,像 SOA 一樣,這些企業開發微服務的時候又陷入了服務粒度,資料移轉,組織形式改變和分散式處理的鬥爭中。
就像很多新技術一樣, 你瞭解的越深,架構風格、反模式、陷阱就會出現,並且這各個過程中得到不少教訓。正如上面所說,反模式就像走到一條正確的路上,走了很久才發現路錯了,而陷阱則是開始沒多久你就會發現路是不對的。
這本書介紹了幾種常用反模式和陷阱,但是明顯的是,不可能將微服務所有的反模式和陷阱在這麼一本薄薄的書中全部介紹。這些常用的反模式和陷阱包括服務粒度 (沙粒陷阱), data migration (資料驅動的遷移反模式), remote access latency ("我們到了嗎"陷阱), reporting (到達報告反模式), contract versioning (靜態契約陷阱), service responsiveness (逾時反模式)等等。
資料驅動的遷移反模式
Data-Driven Migration AntiPattern
微服務會建立大量小的、分布式的、單一用途的服務,每個服務擁有自己的資料。這種服務和資料耦合支援一個有界的上下文和一個無共用資料的架構,其中,每個服務及其對應的資料是獨立一塊,完全獨立於所有其他服務。服務只暴露了一個明確的介面(服務契約)。有界的上下文可以允許開發人員以最小的依賴快速輕鬆地開發,測試和部署。
採用資料驅動的遷移反模式大多是當你從一個單體(monolithic)應用程式到微服務架構遷移的時候。我們將之稱為反模式原因是,它似乎是在開始建立微服務的時候看起來是一個好主意,服務和相應的資料獨立成一個微服務,非常的美好,但正如書中接下來介紹的,這可能會將你引向一個錯誤的道路上,問題是高風險,過剩成本和額外的遷移工作。
單體應用遷移到微服務架構有兩個主要目標:第一個目標是單體應用程式的功能分割成小的,單一用途的服務。第二個目標是單體應用的資料移轉到每個服務自己獨佔的小資料庫(或獨立的服務)。
如所示,理想很豐滿,但是現實很骨感。
太多的資料移轉
這種遷移最主要的問題是你很難一次將資料庫的粒度很好的分割成獨立的每個微服務獨佔的資料。
當然你開始可以分割成粗粒度的資料和服務,然後再進一步的分割成更小的微服務和資料,你可能要頻繁地進行服務調整:粒度太小,合并微服務;粒度太大,分割成更小的微服務。資料移轉要比原始碼遷移更複雜,更容易出錯,理想情況下只為微服務遷移資料一次。理解資料移轉的風險性是避免這種反模式的第一步。
功能分割優先,資料移轉最後
避免這種反模式的主要技術就是首先遷移功能,然後再考慮劃分這個微服務和它的資料的有界上下文。
逾時反模式
微服務是一種分布式的架構,它所有的組件(也就是服務)會被部署為單獨的應用程式,並通過某種遠端存取協議進行通訊。分布式應用的挑戰之一就是如何管理遠程服務的可用性和它們的響應。雖然服務可用性和服務響應都涉及到服務的通訊,但它們是兩個完全不同的東西。服務可用性是服務消費者串連服務並能夠發送請求的能力,服務響應則關注服務的回應時間。
如果服務不可用,服務消費者會在毫秒級的時間內得到通知,它可以返回錯誤資訊或者嘗試串連。但是如果服務接收了請求但是不響應,服務消費者該怎麼辦?可以無限等待或者設定一個逾時時間。
逾時看起來也挺不錯,但也會導致反模式。
使用逾時
你可能很困惑,難道不應該設定一個逾時時間嗎?你理解的很對,但是在大部分的情況逾時時間的錯誤設定會帶來問題。比如當你買東西的時候,你提交了訂單,服務一直在處理沒有返回,你在逾時的時候再提交訂單,顯然伺服器需要更複雜的邏輯來處理重複提交訂單的問題。
所以你可能不想設定逾時時間太短,但是多少合適呢?一種基於資料庫的逾時來計算服務的逾時時間,另一種更常用,計算大壓力下最長的處理時間,把它乘以2作為逾時時間。但是這個值可能非常的不合適,有可能使用者覺得等待太久就把頁面關了,所以還的尋找更好的方式。
使用熔斷器設計模式
Circuit Breaker Pattern
這種設計模式就像家裡的電器的保險絲一樣,當負載過大,或者電路發生故障或異常時,電流會不斷升高,為防止升高的電流有可能損壞電路中的某些重要器件或貴重器件,燒毀電路甚至造成火災。保險絲會在電流異常升高到一定的高度和熱度的時候,自身熔斷切斷電流,從而起到保護電路安全啟動並執行作用。
當一個軟體熔斷器監測到服務沒有響應的時候,它就會熔斷,拒絕請求。一旦服務恢複,熔斷器就會接上,允許服務通過。
熔斷器有多種方式監控服務的可用性,最簡單的方式就是心跳檢查,也可以用調用虛擬服務精確監測,還可以即時地監控服務調用的狀態,一旦到達一個閾值,則熔斷器進入限流的狀態。
共用反模式
I Was Taught to Share" AntiPattern
微服務是一種無共用的架構,我更傾向於叫它為"盡量不分享"模式(share-as-little-as-possible), 因為總有一些代碼會在微服務之間共用。比如不提供一個身分識別驗證的微服務,而是將身分識別驗證的代碼打包成一個jar檔案:security.jar,其它服務都能使用。如果安全檢查是服務等級的功能,每個服務接收到請求都會檢查安全性,這種方式可以很好的提高效能。
但是這容易引起"依賴噩夢":
太多依賴
如果你使用物件導向的開發語言,肯定會遇到下面的繼承關係,成百的模組拆成微服務的時候都會依賴很多的共用庫。
微服務的目標之一就是盡量少的共用,這會協助微服務確定它的有界上下文,服務更容易的測試和部署。服務之間依賴越多,服務則更難被隔離。
共用代碼的技術
說起來容易做起來難,介紹了代碼共用帶來問題的四種情境。
前三種的共用代碼的方式好理解,第四種方式是將共用代碼的邏輯分割出來,做成一個單獨的服務,比如驗證服務。
對於共用庫來說,不要把所有共用程式碼放在一個庫中,比如common.jar,而是根據它們的邏輯劃分成更小的庫,比如security.jar, persistence.jar, dateutils.jar。
到達報告反模式
Reach-in Reporting AntiPattern
有四種方式可以處理微服務架構中的報告。
- database pull model
- HTTP pull model
- batch pull model
- event-based push model
前三個模型都是從微服務自己的資料庫中拉取資料,所以這個反模式就叫"rearch-in reporting"。既然前三種會出現這中反模式,我們就先看看為什麼它們會帶來麻煩。
微服務報告的問題
問題有兩面:
- 如何定時的擷取報告的資料?
- 仍然保持著微服務和資料的的有界上下文?
是database pull model,直接存取資料庫,這會帶來資料庫的非獨立性。
是HTTP pull model,微服務提供資料報告介面,但是會影響微服務的效能,尤其是複雜報告的查詢。
是batch pull model,批次程式成批地將微服務的資料倒入到報告的資料庫中,但是問題和HTTP pull model一樣,這會帶來資料庫的非獨立性,微服務資料庫格式的改變也會影響報表服務。
Asynchronous Event Pushing
是event-based push model,也叫 data pump。雖然相對複雜,但是不會違背本節開始提出的兩個問題。
沙粒陷阱
Grains of Sand Pitfall
架構師和開發人員在採用微服務架構的時候最大的挑戰之一就是服務粒度的問題。微服務的服務粒度多大合適?服務粒度至關重要,它會影響應用的效能、健壯性、可靠性、可測性、設定發布模型。
當服務的粒度太小的時候就會遇到沙粒陷阱。微服務的微並不意味著服務越小越好,但是多小是小?
如果你檢視一下微服務實現的項目,會發現很多一個類實現的微服務,在這種情況下會容易遇到沙粒陷阱。
當然微服務的粒度並不是靠服務實現的類的數量所決定的,有些服務很簡單,只需一個簡單的類就可以實現,而有些確需要更多的類。既然類的數量不能用來決定微服務的粒度,那麼用什麼標準來衡量微服務的粒度是合適的呢?主要依賴:服務的範圍(scope)和功能(functionality)、資料庫事務的需求以及服務編排的等級。
分析服務的範圍和功能
服務要做什嗎?有那些操作?
完整性(Cohesion)扮演了很重要的角色。比如一個顧客服務(customer service)有下面的操作:
- add_customer
- update_customer
- get_customer
- notify_customer
- record_customer_comments
- get_customer_comments
前三個操作是相關的,它們用來管理和維護顧客資訊。但是後三個並不和基本的CRUD操作相關。在分析這個服務的完整性的時候,我們就比較清晰了,這個服務可以被分成三個服務:顧客資訊服務、顧客通知服務和顧客評論服務。
Sam Newman提供了一個很好的可操作的方法,開始不妨將服務劃分成粗粒度的服務,隨著對服務瞭解更多,再進一步劃分成更小粒度的服務。
分析資料庫事務
資料庫事務更正式的叫做 ACID 事務 (atomicity, consistency, isolation, and durability)。ACID事務封裝多個資料庫更新為一個工作單元,工作單元要不整體完成,要不就出現錯誤而復原。
因為微服務架構中服務是分布式的獨立的應用,再兩個或者多個服務之間維護 ACID 事務就極度困難,所以微服務架構中經常會依賴 BASE (basic availability, soft state, and eventual consistency)。儘管如此,你還是再特定的服務中要使用 ACID 事務。當你需要在 ACID vs. BASE 事務中做艱難的決定的時候,可能你的服務劃分的就太細了。
當發現不能使用最終一致性時,你通常就會把服務從細粒度調整為粗粒度的服務,。
分析服務編排
第三個衡量方式是分析服務編排。服務編排是指服務之間的通訊,通常也指內部服務通訊。
遠程調用服務是需要花時間的,它會降低應用整體的效能。再者,它也會影響服務的健壯性和可靠性。
如果你發現完成一個邏輯請求需要調用太多的服務時,服務的劃分可能粒度就太小了。
整合服務、合并到更粗粒度可以提升應用的整體效能,提高應用的健壯性和可靠性。你還可以移除服務之間的依賴,可以更好的控制、測試和發布。
當然你可能會說調用多個服務可以並行的執行,提到應用的響應,比如 reactive 架構的非同步編程方式, 關鍵還是要權衡利弊, 確保對使用者的及時響應以及系統整體的可靠性。
無因的開發人員陷阱
Developer Without a Cause Pitfall
名字來自詹姆斯·迪恩演的電影《無因的反叛》(Rebel Without a Cause),一個問題青年因為錯誤的原因做了錯誤的決定。
很多架構師和開發人員在微服務的開發中權衡利弊, 比如服務粒度和營運工具,但是基於錯誤的原因,做了錯誤的決定。
就是一個情境。服務被認為粒度太細,影響效能和可靠性,所以要遷移到一個單一的粒度更粗的服務上。
看起來合情合理,但是沒有考慮tradeoff。發布、改變控制、測試都深受影響。
下面正好相反,將粗粒度服務劃分成細粒度的服務。
如果你不考慮這種改變帶來的tradeoff,可能影響是很大的。
作者指出,要深刻理解選擇微服務後面的商業驅動。
隨大流陷阱
Jump on the Bandwagon Pitfall
因為微服務是現在的潮流,所以你選擇了微服務,還沒有仔細的分析你的商業需求、商業驅動、組織架構和技術環境,這就是隨大流陷阱。
微服務並不適合所有的情境。
避免這個陷阱的方式充分理解微服務的好處和短處,俗話說,知己知彼,百戰不殆。
好處:
- 發布:易於發布
- 測試:易於測試
- 改變控制:更容易的改變一個服務的功能
- 模組-
- 規模可擴充
短處
所以理解了微服務的優缺點,結合自己的實際情況,來決定是否要採用微服務。
其它架構模式
微服務的架構很好,但是不是唯一的架構模式,比如下面還有一些其它的架構模式:
- Service-Based Architecture
- Service-Oriented Architecture
- Layered Architecture
- Microkernel Architecture
- Space-Based Architecture
- Event-Driven Architecture
- Pipeline Architecture
當然你並不一定只使用唯一的一種架構模式,你可能在系統中混用這些架構模式。
下面有一些架構的參考資料:
- Software Architecture Fundamentals: Understanding the Basics
- Software Architecture Fundamentals: Beyond the Basics
- Software Architecture Fundamentals: Service-Based Architecture
- Software Architecture Patterns
- Microservices vs. Service-Oriented Architecture
靜態契約陷阱
The Static Contract Pitfall
這一節主要講服務的版本控制。入股服務一開始就沒有考慮版本控制,服務的schema發生變化時,或者內部實現邏輯有變化時消費者和伺服器之間的通訊和業務處理就會發生問題。
所以你要為你的服務設計版本號碼。
有兩種實現方式,在header中加入版本號碼,或者在服務的schema中加入版本號碼。
我們到了嗎陷阱
Are We There Yet Pitfall
這個陷阱發生在你不知道遠程調用要花多長時間的情況。50秒?平均多長時間呢,長尾的延遲呢?
首先你應該測量服務的調用時間,至少能知道一個服務遠程調用的大概時間。
然後你應該評估不同的服務通訊協議的效能,比如REST、JMS、AMQP等。
當然效能也不是唯一個衡量遠程通訊協議的因素,比如下面一節中講到的內容。
REST陷阱
使用REST風格非常的流行,大部分的軟體架構也選擇它作為通訊的方式,比如DropWizard, Spring Boot等,有興趣的讀者可以閱讀我寫的Java RESTful架構的效能比較。
既然大家都在用它,還怎麼是個陷阱呢?
如果把REST作為唯一的通訊方式,就有可能掉入這個陷阱。比如如何處理非同步通訊(http 1.1是blocking的)、如何在一個事務中管理多次服務調用?如何支援廣播?
你應該考慮兩種類型的訊息標準作為微服務架構中的訊息傳遞:特定平台的標準和平台無關的標準。
特定平台的標準比如 JMS for java、MSMQ for .net。平台無關的比如 AMQP。
使用訊息系統的好處可以非同步請求,還可以實現廣播的方式,還可以實現事務請求。
作者是 Java Message Service 第二版的作者之一,所以對訊息系統有自己的見解。