微服務的反模式和陷阱

       前幾天我寫了篇讀書筆記: 《產品級微服務的八大原則》,介紹了Uber的SRE工程師 Susan J. Fowler 的免費書:Microservices in Production,文中提出了一個微服務成功與否的惟一標準就是可用性,很是有實踐意義。可是這本書偏向於從 SRE (site reliability engineer)的視角看待微服務,對於開發工程師 (SWE, software engineer)來講,更關注的是如何正確地從單體程序重構到微服務架構,或者從頭設計微服務架構, 這篇讀書筆記主要就是介紹這方面的實踐和經驗。java

Oreilly 的 的這本免費小書 Microservices AntiPatterns and Pitfalls由經驗豐富的 Mark Richards 編寫。書中將反模式(AntiPattern)定義爲"起初看起來很美好,作到最後麻煩不斷的實踐模式",而將陷阱(Pitfall)定義爲「起初看起來就不是一個好的設計」,書中列舉了微服務開發中幾種常見的反模式和陷阱,這些經驗很是的接地氣.他還提供了視頻教程mysql

2006年 SOA (service-oriented architecture) 狂熱流行,無數的公司隨着技術潮流擁抱 SOA, 無論它們是否已經徹底理解了這種複雜的架構風格的長處和短處,這些公司不可避免地陷入和服務粒度、性能、數據遷移以及SOA組織改變的鬥爭中,不少公司不得不放棄了 SOA, 或者折衷的創建了一種混雜的架構風格。react

歷史的悲劇又將重演。微服務是行業當前的發展趨勢。如今,微服務就像2000年中期的 SOA 同樣是技術的熱潮。不少公司都在朝着微服務架構的方式改變,以期得到微服務架構的好處,例如便於測試,快速,易於部署,細粒度的可擴展性,模塊化和敏捷性。然而,像 SOA 同樣,這些企業開發微服務的時候又陷入了服務粒度,數據遷移,組織形式改變和分佈式處理的鬥爭中。git

就像不少新技術同樣, 你瞭解的越深,架構風格、反模式、陷阱就會出現,而且這各個過程當中獲得很多教訓。正如上面所說,反模式就像走到一條正確的路上,走了好久才發現路錯了,而陷阱則是開始沒多久你就會發現路是不對的。spring

這本書介紹了幾種經常使用反模式和陷阱,可是明顯的是,不可能將微服務全部的反模式和陷阱在這麼一本薄薄的書中所有介紹。這些經常使用的反模式和陷阱包括服務粒度 (沙粒陷阱), data migration (數據驅動的遷移反模式), remote access latency ("咱們到了嗎"陷阱), reporting (到達報告反模式), contract versioning (靜態契約陷阱), service responsiveness (超時反模式)等等。sql

數據驅動的遷移反模式

Data-Driven Migration AntiPattern數據庫

微服務會建立大量小的、分佈式的、單一用途的服務,每一個服務擁有本身的數據。這種服務和數據耦合支持一個有界的上下文和一個無共享數據的架構,其中,每一個服務及其對應的數據是獨立一塊,徹底獨立於全部其餘服務。服務只暴露了一個明確的接口(服務契約)。有界的上下文能夠容許開發者以最小的依賴快速輕鬆地開發,測試和部署。編程

採用數據驅動的遷移反模式大可能是當你從一個單體(monolithic)應用程序到微服務架構遷移的時候。咱們將之稱爲反模式緣由是,它彷佛是在開始建立微服務的時候看起來是一個好主意,服務和相應的數據獨立成一個微服務,很是的美好,但正如書中接下來介紹的,這可能會將你引向一個錯誤的道路上,問題是高風險,過剩成本和額外的遷移工做。設計模式

單體應用遷移到微服務架構有兩個主要目標:第一個目標是單體應用程序的功能分割成小的,單一用途的服務。第二個目標是單體應用的數據遷移到每一個服務本身獨佔的小數據庫(或獨立的服務)。安全

圖片來源: Oreilly圖片來源: Oreilly

如上圖所示,理想很豐滿,可是現實很骨感。

太多的數據遷移

這種遷移最主要的問題是你很難一次將數據庫的粒度很好的分割成獨立的每一個微服務獨佔的數據。

固然你開始能夠分割成粗粒度的數據和服務,而後再進一步的分割成更小的微服務和數據,你可能要頻繁地進行服務調整:粒度過小,合併微服務;粒度太大,分割成更小的微服務。數據遷移要比源代碼遷移更復雜,更容易出錯,理想狀況下只爲微服務遷移數據一次。理解數據遷移的風險性是避免這種反模式的第一步。

功能分割優先,數據遷移最後

避免這種反模式的主要技術就是首先遷移功能,而後再考慮劃分這個微服務和它的數據的有界上下文。
圖片來源: Oreilly圖片來源: Oreilly

超時反模式

微服務是一種分佈式的架構,它全部的組件(也就是服務)會被部署爲單獨的應用程序,並經過某種遠程訪問協議進行通信。分佈式應用的挑戰之一就是如何管理遠程服務的可用性和它們的響應。雖然服務可用性和服務響應都涉及到服務的通訊,但它們是兩個徹底不一樣的東西。服務可用性是服務消費者鏈接服務並可以發送請求的能力,服務響應則關注服務的響應時間。

圖片來源: Oreilly圖片來源: Oreilly

若是服務不可用,服務消費者會在毫秒級的時間內獲得通知,它能夠返回錯誤信息或者嘗試鏈接。可是若是服務接收了請求可是不響應,服務消費者該怎麼辦?能夠無限等待或者設置一個超時時間。

超時看起來也挺不錯,但也會致使反模式。

使用超時

你可能很困惑,難道不該該設置一個超時時間嗎?你理解的很對,可是在大部分的狀況超時時間的錯誤設置會帶來問題。好比當你買東西的時候,你提交了訂單,服務一直在處理沒有返回,你在超時的時候再提交訂單,顯然服務器須要更復雜的邏輯來處理重複提交訂單的問題。

因此你可能不想設置超時時間過短,可是多少合適呢?一種基於數據庫的超時來計算服務的超時時間,另外一種更經常使用,計算大壓力下最長的處理時間,把它乘以2做爲超時時間。可是這個值可能很是的不合適,有可能用戶以爲等待過久就把頁面關了,因此還的尋找更好的方式。

使用熔斷器設計模式

Circuit Breaker Pattern

這種設計模式就像家裏的電器的保險絲同樣,當負載過大,或者電路發生故障或異常時,電流會不斷升高,爲防止升高的電流有可能損壞電路中的某些重要器件或貴重器件,燒燬電路甚至形成火災。保險絲會在電流異常升高到必定的高度和熱度的時候,自身熔斷切斷電流,從而起到保護電路安全運行的做用。

當一個軟件熔斷器監測到服務沒有響應的時候,它就會熔斷,拒絕請求。一旦服務恢復,熔斷器就會接上,容許服務經過。

圖片來源: Oreilly圖片來源: Oreilly

熔斷器有多種方式監控服務的可用性,最簡單的方式就是心跳檢查,也能夠用調用虛擬的服務精確監測,還能夠實時地監控服務調用的狀態,一旦到達一個閾值,則熔斷器進入限流的狀態。

共享反模式

I Was Taught to Share" AntiPattern

微服務是一種無共享的架構,我更傾向於叫它爲"儘可能不分享"模式(share-as-little-as-possible), 由於總有一些代碼會在微服務之間共享。好比不提供一個身份驗證的微服務,而是將身份驗證的代碼打包成一個jar文件:security.jar,其它服務都能使用。若是安全檢查是服務級別的功能,每一個服務接收到請求都會檢查安全性,這種方式能夠很好的提升性能。

可是這容易引發"依賴噩夢":

圖片來源: Oreilly圖片來源: Oreilly

太多依賴

若是你使用面向對象的開發語言,確定會遇到下面的繼承關係,成百的模塊拆成微服務的時候都會依賴不少的共享庫。

圖片來源: Oreilly圖片來源: Oreilly

微服務的目標之一就是儘可能少的共享,這會幫助微服務肯定它的有界上下文,服務更容易的測試和部署。服務之間依賴越多,服務則更難被隔離。

共享代碼的技術

提及來容易作起來難,下圖介紹了代碼共享帶來問題的四種場景。

圖片來源: Oreilly圖片來源: Oreilly

前三種的共享代碼的方式好理解,第四種方式是將共享代碼的邏輯分割出來,作成一個單獨的服務,好比身份驗證服務。

對於共享庫來講,不要把全部共享的代碼放在一個庫中,好比common.jar,而是根據它們的邏輯劃分紅更小的庫,好比security.jarpersistence.jardateutils.jar

到達報告反模式

Reach-in Reporting AntiPattern

有四種方式能夠處理微服務架構中的報告。

  • database pull model
  • HTTP pull model
  • batch pull model
  • event-based push model

前三個模型都是從微服務本身的數據庫中拉取數據,因此這個反模式就叫"rearch-in reporting"。既然前三種會出現這中反模式,咱們就先看看爲何它們會帶來麻煩。

微服務報告的問題

問題有兩面:

  1. 如何定時的獲取報告的數據?
  2. 仍然保持着微服務和數據的的有界上下文?

下圖是database pull model,直接訪問數據庫,這會帶來數據庫的非獨立性。
圖片來源: Oreilly圖片來源: Oreilly

下圖是HTTP pull model,微服務提供數據報告接口,可是會影響微服務的性能,尤爲是複雜報告的查詢。
圖片來源: Oreilly圖片來源: Oreilly

下圖是batch pull model,批處理程序成批地將微服務的數據倒入到報告的數據庫中,可是問題和HTTP pull model同樣,這會帶來數據庫的非獨立性,微服務數據庫格式的改變也會影響報告服務。
圖片來源: Oreilly圖片來源: Oreilly

Asynchronous Event Pushing

下圖是event-based push model,也叫 data pump。雖然相對複雜,可是不會違背本節開始提出的兩個問題。
圖片來源: Oreilly圖片來源: Oreilly

沙粒陷阱

Grains of Sand Pitfall

架構師和開發人員在採用微服務架構的時候最大的挑戰之一就是服務粒度的問題。微服務的服務粒度多大合適?服務粒度相當重要,它會影響應用的性能、健壯性、可靠性、可測性、設置發佈模型。

當服務的粒度過小的時候就會遇到沙粒陷阱。微服務的微並不意味着服務越小越好,可是多小是小?

若是你檢視一下微服務實現的項目,會發現不少一個類實現的微服務,在這種狀況下會容易遇到沙粒陷阱。

固然微服務的粒度並非靠服務實現的類的數量所決定的,有些服務很簡單,只需一個簡單的類就能夠實現,而有些確須要更多的類。既然類的數量不能用來決定微服務的粒度,那麼用什麼標準來衡量微服務的粒度是合適的呢?主要依賴:服務的範圍(scope)和功能(functionality)、數據庫事務的需求以及服務編排的等級。

分析服務的範圍和功能

服務要作什麼?有那些操做?

完整性(Cohesion)扮演了很重要的角色。好比一個顧客服務(customer service)有下面的操做:

  • add_customer
  • update_customer
  • get_customer
  • notify_customer
  • record_customer_comments
  • get_customer_comments

前三個操做是相關的,它們用來管理和維護顧客信息。可是後三個並不和基本的CRUD操做相關。在分析這個服務的完整性的時候,咱們就比較清晰了,這個服務能夠被分紅三個服務:顧客信息服務、顧客通知服務和顧客評論服務。

圖片來源: Oreilly圖片來源: Oreilly

Sam Newman提供了一個很好的可操做的方法,開始不妨將服務劃分紅粗粒度的服務,隨着對服務瞭解更多,再進一步劃分紅更小粒度的服務。

分析數據庫事務

數據庫事務更正式的叫作 ACID 事務 (atomicity, consistency, isolation, and durability)。ACID事務封裝多個數據庫更新爲一個工做單元,工做單元要不總體完成,要不就出現錯誤而回滾。

由於微服務架構中服務是分佈式的獨立的應用,再兩個或者多個服務之間維護 ACID 事務就極度困難,因此微服務架構中常常會依賴 BASE (basic availability, soft state, and eventual consistency)。儘管如此,你仍是再特定的服務中要使用 ACID 事務。當你須要在 ACID vs. BASE 事務中作艱難的決定的時候,可能你的服務劃分的就太細了。

當發現不能使用最終一致性時,你一般就會把服務從細粒度調整爲粗粒度的服務,如圖所示。

圖片來源: Oreilly圖片來源: Oreilly

分析服務編排

第三個衡量方式是分析服務編排。服務編排是指服務之間的通信,一般也指內部服務通信。
遠程調用服務是須要花時間的,它會下降應用總體的性能。再者,它也會影響服務的健壯性和可靠性。

若是你發現完成一個邏輯請求須要調用太多的服務時,服務的劃分可能粒度就過小了。

圖片來源: Oreilly圖片來源: Oreilly

整合服務、合併到更粗粒度能夠提高應用的總體性能,提升應用的健壯性和可靠性。你還能夠移除服務之間的依賴,能夠更好的控制、測試和發佈。

固然你可能會說調用多個服務能夠並行的執行,提到應用的響應,好比 reactive 架構的異步編程方式, 關鍵仍是要權衡利弊, 確保對用戶的及時響應以及系統總體的可靠性。

無因的開發者陷阱

Developer Without a Cause Pitfall

名字來自詹姆斯·迪恩演的電影《無因的反叛》(Rebel Without a Cause),一個問題青年由於錯誤的緣由作了錯誤的決定。

不少架構師和開發者在微服務的開發中權衡利弊, 好比服務粒度和運維工具,可是基於錯誤的緣由,作了錯誤的決定。

下圖就是一個場景。服務被認爲粒度太細,影響性能和可靠性,因此要遷移到一個單一的粒度更粗的服務上。

圖片來源: Oreilly圖片來源: Oreilly

看起來合情合理,可是沒有考慮tradeoff。發佈、改變控制、測試都深受影響。

下面正好相反,將粗粒度服務劃分紅細粒度的服務。

圖片來源: Oreilly圖片來源: Oreilly

若是你不考慮這種改變帶來的tradeoff,可能影響是很大的。

做者指出,要深入理解選擇微服務後面的商業驅動。

隨大流陷阱

Jump on the Bandwagon Pitfall

由於微服務是如今的潮流,因此你選擇了微服務,尚未仔細的分析你的商業需求、商業驅動、組織架構和技術環境,這就是隨大流陷阱。

微服務並不適合全部的場景。

避免這個陷阱的方式充分理解微服務的好處和短處,俗話說,知己知彼,百戰不殆。

好處:

  • 發佈:易於發佈
  • 測試:易於測試
  • 改變控制:更容易的改變一個服務的功能
  • 模塊-
  • 規模可擴展

短處

  • Team組織改變
  • 性能
  • 可靠性下降
  • 運維難度加大

因此理解了微服務的優缺點,結合本身的實際狀況,來決定是否要採用微服務。

其它架構模式

微服務的架構很好,可是不是惟一的架構模式,好比下面還有一些其它的架構模式:

  • Service-Based Architecture
  • Service-Oriented Architecture
  • Layered Architecture
  • Microkernel Architecture
  • Space-Based Architecture
  • Event-Driven Architecture
  • Pipeline 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。

使用消息系統的好處能夠異步請求,還能夠實現廣播的方式,還能夠實現事務請求。

相關文章
相關標籤/搜索