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