
微服務架構(gòu)相比單體架構(gòu)而言的優(yōu)點,可以列舉出很多:服務個體更小,更內(nèi)聚,業(yè)務職責更清晰,可復用性更強,可以獨立部署發(fā)布等等;從軟件開發(fā)的角度,靈活性和效率都會有很大的提升……
然而,微服務架構(gòu)本質(zhì)上是分布式系統(tǒng)架構(gòu),各個服務需要配合協(xié)同來完成產(chǎn)品的需求,業(yè)務數(shù)據(jù)的分散使得服務之間需要通過集成來完成協(xié)同工作,那么問題來了,集成要采用什么方式?要以什么樣的原則進行?如何設計才能盡量保證不同服務之間數(shù)據(jù)的一致性?
微服務架構(gòu)下推薦使用REST作為服務間同步通信的方式,對于非實時需求,可以基于事件來實現(xiàn)異步協(xié)作。這個原則非常簡單,也非常容易實現(xiàn),然而僅遵循這個原則卻很難讓我們沿著微服務的道路走下去。
一些號稱微服務架構(gòu)的系統(tǒng),最初服務拆分并無太大問題,但隨著邏輯的不斷擴展,跨服務數(shù)據(jù)交換場景的增加,這個簡單的原則就很難指引日常的決策,甚至引發(fā)了一些新的問題,舉幾個例子。
1.1 服務循環(huán)依賴
在一些場景下服務A依賴服務B,會調(diào)用服務B的API,而在另外一些場景下,服務B也需要服務A的數(shù)據(jù),也會通過調(diào)用服務A的API來實現(xiàn)。單從實現(xiàn)層面來看,按需獲取數(shù)據(jù),實現(xiàn)很方便,可以完成業(yè)務要達成的效果;但從長遠來看,兩個服務的耦合越來越緊,未來新增需求的實現(xiàn)成本會越來越高,成為技術債。
1.2 第三方系統(tǒng)集成實現(xiàn)到業(yè)務服務中
在一些業(yè)務場景下,當前產(chǎn)品需要的業(yè)務數(shù)據(jù)需要從幾個第三方系統(tǒng)獲取并進行整合甚至經(jīng)過一些計算后才能使用,之前的一些項目在初期實現(xiàn)時,數(shù)據(jù)從第三方系統(tǒng)獲取后,經(jīng)過處理直接寫入業(yè)務數(shù)據(jù)庫,就把集成的代碼直接寫到業(yè)務服務中,看起來并沒有太大的問題,只要代碼上做好隔離就好了。
但實際上,后期發(fā)生了一些棘手的問題讓我們不得不作出改變:
- 一些定時觸發(fā)的集成任務,每次只會在一個服務實例上運行,在運行期間可能會有大量的數(shù)據(jù)讀取、計算、更新、插入等操作,會短時大量占用當前服務實例的資源,嚴重的情況下當前服務實例甚至無法正常對外提供服務。
- 相似的集成以同樣的方式集成到業(yè)務服務中,比如不同品牌的產(chǎn)品庫存會從多個第三方系統(tǒng)獲取,業(yè)務服務變的臃腫。
- 集成方的一些變化直接影響到業(yè)務服務,比如API的升級,這種改變必須要重新升級部署業(yè)務服務才能完成。
無論在第三方系統(tǒng)集成和內(nèi)部服務的調(diào)用過程中,接口不冪等都會造成數(shù)據(jù)不一致的問題。最典型的場景是服務A調(diào)用服務B的接口更新或?qū)懭霐?shù)據(jù),服務B處理成功,但由于網(wǎng)絡原因,服務A沒有收到服務B的響應,服務A重試調(diào)用接口,此時,服務B由于接口不冪等返回異常的響應,導致業(yè)務流程無法繼續(xù)下去。理想是豐滿的,現(xiàn)實是骨感的,大多數(shù)的項目開始于滿懷激情的整潔架構(gòu)的夢想,而開發(fā)工作并不像想象的那么順利,伴隨著項目人員更替、交付壓力大、人員能力不足等等,架構(gòu)開始逐漸走向大家不期望的方向,技術債臺高筑,攻城獅們疲于奔命的追趕進度的同時,只能望債臺興嘆。
當問題發(fā)生時,架構(gòu)師們都能第一時間站出來說這個設計太爛了,怎么能做成這樣;事后諸葛是我們積累經(jīng)驗的重要手段,從高筑的技術債臺,從疲于救火的線上問題,從接了新需求確找不到合理方案……我們一直在總結(jié)經(jīng)驗,為了避免再次摔倒在同一個地方,有哪些架構(gòu)設計原則可以先行呢?
微服務拆分之初都定義了各個服務的上下游關系,我們可以定義上下游服務的依賴關系如下:- 下游服務可以直接依賴上游服務,下游服務可以通過上游服務提供的API同步對上游服務的數(shù)據(jù)進行讀寫。
- 上游服務不依賴下游服務,上游服務數(shù)據(jù)狀態(tài)變化如果會對下游服務產(chǎn)生影響,可以通過發(fā)布領域事件,由下游系統(tǒng)監(jiān)聽事件作出對應的操作。
微服務的各個領域?qū)嶓w之間存在著各式各樣的關系,當領域?qū)嶓wA依賴領域?qū)嶓wB的信息時,通常需要在領域?qū)嶓wA中保留一部分領域?qū)嶓wB的副本信息,那這份副本信息中該包含哪些信息呢?舉個例子,領域?qū)嶓wA是訂單,領域?qū)嶓wB是客戶,客戶端每次查詢訂單信息時,都要求同時查出客戶的姓名、性別和手機號碼,將這3個關鍵信息冗余在訂單的服務中就可以輕松滿足需求了,一切聽起來是那么的美好。然而,這3個關鍵信息如果是可以變化的,那么問題就來了,如果領域?qū)嶓wB中的信息更新了,領域?qū)嶓wA中的信息要不要更新,如果不更新會造成數(shù)據(jù)不一致,如果更新無端增加了復雜度,而很有可能因此而產(chǎn)生循環(huán)依賴。因此,在無法確定數(shù)據(jù)變化的準確情況時,在副本中只保留引用信息最保險,需要完整信息時可以通過引用查詢相關信息。2.3 為第三方系統(tǒng)構(gòu)建外觀服務面對第三方系統(tǒng)集成,在系統(tǒng)集成過程中要考慮的更周全,一般情況下,我們無法控制第三方系統(tǒng),一旦集成出現(xiàn)問題,需要溝通解決方案,排期開發(fā),聯(lián)調(diào)測試等等,修復周期很長。另一個問題是第三方系統(tǒng)通常采用的技術棧或集成方式可能和我們的系統(tǒng)完全不同。比如它可能是個非常老舊的系統(tǒng),提供的接口是基于XML的SOAP;再比如它可能無法提供API,只能通過導出文件到某個SFTP或發(fā)送郵件的方式進行集成……為了讓第三方系統(tǒng)集成產(chǎn)生盡量小的影響,我們傾向于構(gòu)建外觀服務來隱藏第三方系統(tǒng)實現(xiàn)的細節(jié),通過外觀服務對第三方系統(tǒng)的功能進行包裝,提供和當前系統(tǒng)更加一致的集成方式。我們可以把外觀服務和第三方系統(tǒng)看做是一個整體,那么第三方系統(tǒng)的集成對于當前系統(tǒng)內(nèi)的服務來說,服務間的集成就可以和內(nèi)部服務一樣處理。而對于第三方系統(tǒng)集成相關的細節(jié)內(nèi)容被隔離在外觀服務中,第三方系統(tǒng)集成的變化,只需要修改外觀服務就可以了。這個原則非常簡單,但實現(xiàn)過程中非常容易被忽略,如果不在設計好的測試用例中,測試過程中也很難發(fā)現(xiàn)問題;多數(shù)情況下是到了線上,用戶使用的真實場景才會發(fā)現(xiàn)問題,這時已經(jīng)產(chǎn)生了業(yè)務影響。因此在涉及集成的接口中,要特別關注,業(yè)務上是否要求接口的冪等性,接口不冪等的情況下業(yè)務是否能夠正常的流轉(zhuǎn)。服務提供的接口通常不只有一個消費者,不同消費者關心的信息往往可能也不一樣,隨著服務數(shù)量的增加,在沒有契約測試的情況下,很有可能發(fā)生因為一個需求修改了當前服務接口的實現(xiàn),而影響其他已有功能。而往往這個問題直到全面回歸的時候才能完全發(fā)現(xiàn),甚至可能發(fā)生因改動范圍很小,回歸不夠,在上線前也難以發(fā)現(xiàn)。契約測試可以很好的解決這個問題,一方面測試前置,盡早提供反饋,另一方面也起到了架構(gòu)守護的作用。
系統(tǒng)集成是分布式系統(tǒng)中一定會談及的問題,而且是個大問題,因為它直接影響到當前系統(tǒng)的架構(gòu),一個微不足道的改動很可能就破壞了整個系統(tǒng)架構(gòu)的原則,久而久之原則便形同虛設。
微服務架構(gòu)也不例外,在缺少架構(gòu)約束的情況下,只圖一時之快的實現(xiàn)往往會葬送了微服務的優(yōu)勢,一個個微小的不合理改動會逐漸將整個架構(gòu)大廈摧毀,所謂千里之堤,潰于蟻穴就是這個道理。因此,在微服務架構(gòu)設計之初,我們就要在團隊內(nèi)建立一些原則,明確系統(tǒng)間集成需要遵守的一些規(guī)范,并且在實踐過程中定期review,必要時可以采用一些架構(gòu)守護的輔助工具,來保護架構(gòu)的健康度。
本周四晚8點 ,【冬哥有話說】, SmartIDE第二個大版本發(fā)布,Server版內(nèi)測,開發(fā)者鏡像開源,多端使用漫游開發(fā)!