上一篇:微服務架構基本原理學習筆記(一)
三、微服務架構
從一個已有的單體架構的應用程式開始進行微服務架構的重構往往是一個不錯的選擇,隨著業務量和功能的增加,我們可以考慮使用微服務架構來擴充應用程式中原有的功能,或者每次添加新功能時,都為其創建一個新的微服務,這比從一開始就選擇使用微服務架構進行設計要相對容易一些,因為微服務架構的好處通常不會體現在小型專案中,所以,考慮讓專案持續迭代一段時間,直到我們能夠非常清晰地確定服務的邊界,通過微服務架構來進行功能的劃分,
因此,對于每一個微服務,我們都需要明確它們各自的職責,并定義公共介面,
每個微服務管理各自的資料
前面我們已經介紹過,微服務是自治的并且可以單獨運行和部署,而實作這一特性的關鍵就是確保每個微服務都擁有自己的資料存盤,也就是說,微服務架構中不允許出現多個微服務共享同一個資料的情況,任何想要訪問其它微服務中資料的情況都應該通過公共介面來完成,

你可能在想,如果沒有一個中心的共享資料庫,如果保證資料的一致性呢?尤其是對于事務而言,資料的一致性至關重要,在微服務架構中,我們不能在單個資料庫事務中去更新來自不同的微服務中的資料,我們要么通過較為復雜的分布式事務來實作這一功能,要么通過微服務自己來保證資料的一致性,而后者是通常較為推薦的做法,這就意味著,我們無法保證在一個相對較短的時間內資料的一致性,而是需要等待一段時間之后才能獲得整體狀態一致的資料,具體的操作是,當其中一個微服務的資料更改時,在一個短暫的時間內其它微服務中的資料無法與其保持一致,這個時候,你需要有一種機制能夠應對這種暫時性的資料非一致性,直到所有微服務中的資料最侄訓得一致性,在微服務中使用快取機制是一個不錯的選擇,這可以大大減少從多個微服務聚合資料以執行單個操作的成本,
查看Github上的eShopOnContainers的微服務架構圖,我們可以看到其中不同的微服務都有各自的資料存盤,例如Identity微服務使用了SQL Server資料,Ordering微服務也使用了SQL Server資料庫,但它和Identity微服務使用的不是同一個資料庫,它們之間也不能相互訪問,Basket微服務使用的則是完全不同的Redis快取資料庫,
現在考慮一個實際場景,Ordering微服務中有一個OrderItem實體,它代表用戶的一個訂單,其中包括ProductId、ProductName和UnitPrice,Catalog微服務中有一個CatalogItem實體,它代表商品資訊,其中包括ID、Name和Price,當用戶購買商品時,Ordering微服務中不僅會記錄商品的ID,同時還會將商品的名稱和價格復制到自己的資料庫中,那么,如果將來Catalog微服務中的商品資訊發生了變化,這個資料并不一定會及時地同步到Ordering微服務中,那么,如果我們只在Ordering微服務中存盤商品ID,而當需要的時候通過API去請求Catalog微服務以獲取商品的詳細資訊豈不是更好?不一定!

Ordering微服務中商品資料的冗余反映的是用戶購買商品時的情形,它與存盤在Catalog微服務中商品現在的資訊并非一致,Ordering微服務并不關心商品當前的價格和名稱,它關心的是用戶下單時商品的價格和名稱,所以事實上,這不是真正的資料冗余,這些資料在不同的微服務背景關系中具備不同的含義,微服務之間的這種資料冗余以及不一致性不僅不會導致系統出現問題,相反,它是由不同的業務需求所決定的,
微服務的組成部分
微服務不一定是在單個服務器或虛擬機上運行的單個行程,它通常至少有兩部分組成:一個由代碼構成的WebAPI,而另一個則是資料庫,所以這至少是兩個不同的行程,而且它們通常并不在同一臺服務器或虛擬機中運行,進一步地,如果我們對服務進行橫向擴展,并同時對資料庫進行分片配置,那么一個微服務甚至會在好幾個不同的服務器或虛擬機中運行,而且它可能還需要某種定時任務來執行資料維護,并監聽和觸發各種不同的訊息,因此,實際上一個微服務可能會涉及到多個不同的服務器和行程,所有的這些部分一起構成了一個微服務,

我們對比看一下eShopOnContainers的微服務架構圖,其中的Ordering微服務包含一個名為Ordering.API的WebAPI,另外還有一個名為Ordering.BackgroundTasks的后臺任務,這是兩個獨立運行的行程,我們不必讓微服務的所有代碼都在一個行程中運行,
從概念上講,每個微服務及其公共介面都有明確的邊界定義,資料只能通過這些公共介面訪問,

每個微服務都是可獨立部署的
微服務可獨立部署和存在,這就意味著依賴于該微服務的客戶端程式不需要同時升級,要做到這一點,你必須確保微服務的公共介面始終保持對舊客戶端程式的兼容,實際上,我們通常把這些公共介面稱之為微服務與客戶端之間的協議,協議是不能單方面更改的,你可能會問,隨著業務和需求的增長,這些公共介面怎么能永遠保持不變呢?最簡單的解決辦法是永遠只對公共介面進行增量修改,例如增加新的介面,或在已有的介面上對資料增加新的屬性,
如果不可避免地要對公共介面進行重大調整,可以考慮下面兩種實踐方式:
一是讓開發客戶端的團隊等待新的微服務上線之后再更新客戶端程式,以確保對微服務的調整不會影響到舊客戶端程式的運行,
另外一個推薦的做法是針對不同版本的客戶端程式創建自動化測驗,并將其加入到持續集成構建中,這樣每次部署之前,如果有自動化測驗未完成,構建就會失敗,我們就可以查找原因并分析是否存在兼容性問題,
需要注意的一點是,有一種模式可以引入客戶端和微服務都使用的共享代碼,從而可以非常方便地生成一個客戶端包來簡化微服務的呼叫,我們應該盡量避免使用這種方式,不要將微服務的開發和測驗與客戶端緊密耦合在一起,因為這會導致客戶端對微服務的強依賴而迫使它們同時升級,
如何確定微服務的邊界
如何確定微服務的邊界是一項比較困難的事情,因為錯誤的微服務邊界定義會導致后期系統性能的下降,并且一旦這些微服務部署到了生產環境,后期再做調整就比較困難了,因此,在專案開始之前,值得我們花一些時間來認真考慮如何確定微服務的邊界,
我們前面也介紹過,從已有的單體架構的應用程式開始進行微服務架構設計會使任務變得相對容易一些,你會發現其中有一些模塊本身就與應用程式的其它部分松散耦合,它們之間通過比較清晰的介面訪問資料,這些模塊比較容易轉換成微服務,
另外一種方式是從資料庫層面著手,看看從某些概念上是否可以將部分表組合在一起,形成一個相對獨立的部分,因為每個微服務都擁有自己獨立的資料,所以從資料庫層面抽取相對獨立的部分也是一個好的想法,我們應該盡量避免從多個不同的微服務之間獲取資料,
微服務始終應該圍繞著業務來進行組織,這方面可以參考領域驅動設計(Domain Driven Design)的概念,它推薦從應用程式的背景關系中來確定邊界,并為其定義模型,這意味著不同的微服務將使用不同的模型,即使它們使用的資料看起來沒什么區別,但對應的業務場景卻不同,
在eShopOnContainers微服務架構中,Ordering微服務和Catalog微服務盡管都與商品資訊有關,但它們處在不同的背景關系中,因此它們實際上具有不同的屬性,可以自由地為同一條資訊使用不同的名稱,
在確定微服務的邊界時,你可能會遇到一些陷阱,例如對所有資料庫中的業務表進行簡單的包裝并生成對應的CRUD服務,這些服務充其量只能稱之為資料庫表的物體類,而不能稱之為微服務,因為它們只具有添加和更新物體的方法,而并沒有將與物體相關的業務邏輯包含進來,另一個導致微服務邊界模糊的情況是,當多個微服務相互之間存在回圈依賴關系時,會導致頻繁的相互通信,我們應該盡量避免這種情況的出現,
讓我們詳細了解一下eShopOnContainers微服務架構中的具體實作,來看看以上這些原則在實際應用中是如何體現的,
| Catalog Microservice | 存盤商品的詳細資訊, |
| Basket Microservice | 跟蹤和存盤客戶的購物籃資訊, |
| Ordering Microservice | 處理客戶的訂單資訊, |
| Identity Microservice | 用于處理用戶身份認證, |
- 職責分離,有助于提高彈性,即使Ordering微服務不可用,依然不影響客戶瀏覽商品資訊并將其添加到購物車,
- 不同的微服務都被設計為處理不同的資料量和訪問模式,Catalog微服務需要支持靈活的查詢,以滿足客戶根據各種不同的查詢方式來搜索到想要購買的商品,所以,Catalog微服務需要將資料存盤在支持大量豐富查詢的資料庫中以滿足業務的需要,例如這里選擇了SQL Server,而Basket微服務只需要存盤比較短暫的資料,這些資料甚至都不需要寫入資料庫,所以這里使用了Redis記憶體快取來保存客戶購物車中的資料,Ordering微服務用來處理用戶的訂單資訊,因此對資料的可靠性有非常嚴格的要求,另外它還需要處理一些敏感資料,例如客戶的識訓地址和付款資訊等,所以對安全性也有非常高的要求,Identity微服務用來處理身份驗證,我們將在后面的微服務安全部分對其進行詳細說明,
- 這不是唯一的確定微服務的方式,你可以根據不同的業務需求對其中的功能進行重新組合,就目前來看,這種設計還不錯,
四、構建微服務
當我們開發微服務應用程式時,我們希望它能運行在不同的環境中,例如開發人員希望在自己作業的電腦上運行和除錯代碼;測驗人員希望在臨時搭建的測驗環境中運行,當然也可能是在云上運行;當微服務正式發布之后,它會在我們的生產環境中運行,那么我們如何托管微服務,使其非常方便地運行在不同的環境中呢?
下面列出了幾種不同的方式:
- 傳統的方式是使用虛擬機,你可以為每一個微服務選擇一臺虛擬機,當然如果你的微服務數量特別多的話,這么做成本可能會比較高,你也可以選擇將多個微服務打包到一臺虛擬機上,但是你需要為其中的每一個微服務安裝不同的框架和依賴包,這會讓初始化作業變得較為繁瑣,
- 第二種方式是選擇PASS(Platform as a Service)平臺,有許多云提供商都提供了微服務的托管服務,你只需要專注于微服務的具體實作, PASS平臺負責微服務的管理和基礎設施,并可以為每個內置負載均衡的微服務提供自動擴展和DNS條目,另外還有標準的安全和監控功能等,
- 第三種方式是選擇使用容器,這是當下最流行的方式之一,容器可以將應用程式及其所有的依賴項都打包在一起,然后非常方便地在任何容器主機上輕松移植并運行,容器主機可以是本地作業的電腦,也可以在云上,這大大簡化了開發和部署的任務,示例應用程式eShopOnContainers就使用了容器來構建微服務,具體步驟可以查看Github上的檔案,容器可以使構建和運行微服務的程序變得簡單,如果我們單獨為每一個微服務安裝所有依賴的軟體并配置所有內容,將會是一個非常痛苦的程序,可能需要好幾天的時間,期間也可能會遇到各種各樣的問題,而容器會使這一切變得非常簡單,而且,許多開發工具也允許將除錯器附加到容器內運行的代碼,這對開發人員debug微服務也帶來了很多的便利,
如何開始創建一個微服務
首先,你需要一個用來保存源代碼的倉庫,例如Github,雖然從技術上來說你可以將所有微服務的代碼都保存在同一個源代碼倉庫中,但是如果微服務的數量很多,這會大大增加微服務間的耦合程度,這顯然違背了微服務設計的初衷,
其次,我們還需要一個能夠自動化持續集成構建微服務的系統,每當我們提交代碼到源代碼管理器的時候,它都會自動構建一個新的微服務版本,同時還會執行自動化測驗,如果測驗未通過,則新的構建就會失敗,并自動發訊息給相應的開發人員,測驗是構建微服務的一個非常重要的部分,
測驗微服務
測驗是構建微服務中一個非常關鍵的部分,在構建微服務的程序中,有幾種不同型別的測驗:
- 單元測驗,單元測驗是針對代碼級別的,通常運行比較快,在單元測驗中,我們要盡可能保證高的代碼覆寫率,尤其是那些針對特定業務的邏輯和模塊,
- 集成測驗,也叫做服務級別的測驗,是針對于單個微服務的測驗,通常我們需要將單個微服務部署到服務器中,并進行相應的配置,然后通過呼叫暴露出來的公共介面來測驗微服務的功能是否正常,相對于單元測驗而言,集成測驗更難撰寫,但它們對于微服務的質量和穩定性來說非常有價值,所以值得我們花時間去創建一個測驗框架,然后為每個微服務創建集成測驗,這些測驗也應該作為自動化構建微服務的一部分,
- 端到端測驗,這些測驗是模擬生產環境中所有運行的微服務,我們可以通過UI界面來執行一些關鍵的業務操作流程,以保證盡可能多的微服務之間的協同作業是否能否達到預期的目標,這部分的測驗在撰寫和維護方面更加困難,而且往往很容易出錯,因為任何一個底層邏輯的改變都有可能導致測驗失敗,你也可以嘗試其它型別的測驗來驗證盡可能多的功能,但往往端到端的測驗在驗證某些關鍵功能方面仍然具有價值,例如在冒煙測驗中快速檢測系統關鍵點的功能是否正常,
微服務模板
在創建微服務時,并不是每次都從零開始,從一個標準模板開始創建微服務可以省去很多作業,你可以根據需要在自己的代碼倉庫中維護微服務模板,也可以在某個特定的微服務的基礎上進行改寫,有許多通用的功能都可以標準化,例如:
- 日志(Logging),它將系統中所有微服務的日志集中管理,
- 健康檢查(Health Checking),如果每個微服務都可以報告自身的運行狀態,告知是否正在運行,是否可以與依賴的其它微服務進行通信,這將是一個不錯的設計,
- 配置(Configuration),讓所有微服務都采用統一的方式進行配置不失為一個好的想法,
- 身份認證(Authentication),我們可以使用一些標準的身份認證機制,這可以降低開發程序中一些錯誤配置而導致我們的微服務存在安全漏洞,
- 構建腳本(Build Scripts),使用標準的構建方法可以避免我們少走彎路,例如eShopOnContainers中使用容器,每個微服務都使用一個Docker檔案來生成容器鏡像,
當然,你可以根據需要將某些特定的功能添加到模板中,模板的好處是提供了開箱即用功能(Out of Box),這大大減少了啟動和運行微服務所需的時間,并且還可以確保系統中所有微服務的一致性,不過這種所謂的一致性不應該限制微服務所使用的技術,例如我們不應該限制所有的微服務都使用同一種編程語言,盡管這可以給開發人員在不同的微服務之間作業帶來便利,但是我們不應該限制這種技術自由,微服務的開發團隊可以自由選擇最佳的開發工具,我們的目標是使微服務開發團隊盡可能高效地作業,將時間花在實作微服務的業務需求上,而不是圍繞微服務技術本身,
每個開發人員都應該具備獨立處理和運行一個微服務的能力,例如在集成測驗環境中檢查各個功能點是否正常,而且,每個開發人員也應該能夠在一個完整的系統中測驗他們開發的某個功能,開發人員可以在本地環境中運行所有的內容,也可以在云上直接訪問整個系統,但是無論采用哪種方式,作業流程都應該盡量簡單并可以自動完成,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/554048.html
標籤:架構設計
下一篇:返回列表
