微服務之集成(四)


1. 尋找理想的集成技術

微服務之間通信的方式的選擇非常多樣化,但哪個是正確的呢?SOAP ? XML-RPC ? REST ?  Protocol Buffers?后面會逐一討論。

首先,我們要考慮的是,我們到底希望從這些技術中得到什么。

1.1 避免破壞性修改

有時候,對某個服務做的一些修改會導致該服務的消費方也隨之發生改變。但是,我們希望選用的技術可以盡量避免這種情況的發生。

1.2 保證API的技術無關性

保證微服務之間的通信方式的技術無關性是非常重要的。這就意味着,不應該選擇哪種對微服務的具體實現技術有限制的集成方式。

1.3 使你的服務易於消費方使用

消費方應該很容易的使用我們的服務。理想情況下,消費方應該可以使用任何技術來實現,從另一方面來說,提供一個客戶端庫也可以簡化消費方的使用。但是通常這種庫與其他我們想要得到的東西不可兼得。例如,使用客戶端庫對於消費方來說很方便,但是會造成耦合的增加。

1.4 隱藏內部實現細節

我們不希望消費方與服務的內部實現細節綁定在一起,因為這會增加耦合。所以,所有傾向於暴露內部實現細節的技術都不應該被采用。

2.為用戶創建接口

3.共享數據庫

目前業界最常見的集成形式應該就是數據庫集成了。使用這種方式時,如果其他服務想要從一個服務獲取信息,可以直接訪問數據庫。如果想要修改,也可以直接在數據庫中修改。

這種方式看起來非常簡單,而且可能是最快的集成方式,這也是它這么流行的原因。

但是它有一些缺點。

如圖,使用數據庫集成來訪問和修改數據信息

缺點一,首先,這使得外部系統能夠查看內部實現細節,並與其綁定在一起。存儲在數據庫中的數據結構對所有人來說都是平等的,所有服務都可以完全訪問數據庫。如果我決定為了更好的表示數據或者增加可維護性而修改表結構的話,我的消費方就無法進行工作。

數據庫是一個很大的共享API,但同時也非常不穩定。為了不影響其他服務,我必須非常小心的避免修改與其他服務相關的表結構。這種情況下,通常需要做大量的回歸測試來保證功能的正確性。

缺點二,其次,消費方與特定的技術選擇綁定在了一起。可能現在來看,使用關系型數據庫做存儲是合理的,所以消費方會使用一個合適的驅動(很有可能是與具體數據庫相關的)來與之一起工作。說不定一段時間之后我們會意識到,使用非關系型數據庫才是更好的選擇。

如果消費方和客戶服務非常緊密的綁定在了一起,那么能輕易替換這個數據庫嗎?答案肯定是不能。所以,正如前面所提過的,隱藏實現細節非常重要,因為它可以讓我們的服務擁有一定的自治性,從而可以輕易的修改其內部實現。這關系到松耦合。

缺點三,最后,我們考慮一下行為。肯定會有一部分邏輯負責對客戶進行修改。那么這個邏輯應該放在什么地方呢?如果消費方直接操作數據庫,那么它們都需要對這些邏輯負責。對數據庫進行操作的相似邏輯可能會出現在很多服務中。就是說,如果倉庫,注冊用戶界面,呼叫中心都需要編輯客戶的信息,那么當修復一個bug的時候,你需要修改三個不同的地方,並且對這些修改分別做部署。這關系到內聚性。

我們知道,關於好的微服務的核心原則就是高內聚和松耦合。但是,使用數據庫集成使得這兩者都很難實現。服務之間很容易通過數據庫集成來共享數據,但是無法共享行為。內部表示暴露給了我們的消費方,而且很難做到無破壞性的修改,進而不可避免的導致不敢做任何修改,所以無論如何都要避免這種情況。

在后面的部分中,我們會介紹服務之間不同風格的集成方式,這些方式都可以保證服務的內部實現得以隱藏。

4. 同步和異步

在介紹具體的技術選擇之前,我們先討論一下服務之間如何協作的問題。服務之間的通信應該是同步還是異步呢?這個基礎選擇的不同,會引導我們使用不同的實現。

如果使用同步通信,發起一個遠程服務調用后,調用方會阻塞自己並等待整個操作的完成。如果使用異步通信,調用方不需要等待操作完成就可以返回,甚至可能不需要關心這個操作是否完成。

異步通信對於運行時間比較長的任務來說比較有用,否則就需要在客戶端和服務器之間開啟一個長連接,而這時非常不實際的。

這兩種不同的通信模式有着各自的協作風格,即請求/響應或者基於事件對於請求/響應來說,客戶端發起一個請求,然后等待響應。這種模式能夠與同步通信模式很好的匹配,但異步通信也可以使用這種模式。我們可以發起一個請求,然后注冊一個回調,當服務端操作結束之后,會調用該回調。

對於使用基於事件的協作方式來說,情況會顛倒過來。客戶端不是發起請求,而是發布一個事件,然后期待其他的協作者接收到該消息,並且知道該怎么做。基於事件的系統天生就是異步的。基於事件的協作方式耦合性很低。也就是說,客戶端發布一個事件,但並不需要知道誰或者什么會對此做出響應,這也意味着,你可以在不影響客戶端的請求下對該事件添加新的訂閱者。

5. 編排與協同

在開始對越來越復雜的邏輯進行建模時,我們需要處理跨服務業務流程的問題,而使用微服務時,這個問題會來的更快。

當我們在MusicCorp中創建用戶時,發生了什么:

(1) 在客戶的積分賬戶中創建一條記錄

(2) 通過郵政系統發送一個歡迎禮包

(3) 向客戶發送歡迎電子郵件

當我們在考慮具體實現時,有兩種架構風格可以采用。

第一種,使用編排。我們會依賴於某個中心大腦來指導並驅動整個流程。第二種,使用協同。我們僅僅會告知系統中各個部分各自的職責,而把具體怎么做的細節留給它們自己。

可以想象在編排的解決方案中,會讓客戶服務未做大腦中心。

在創建時,它會跟積分賬戶、電子郵件服務及郵政服務通過請求響應的方式進行通信。客戶服務本身可以對當前進行到了哪一步進行跟蹤。

編排方式的缺點是,客戶服務作為中心控制點承擔了太多的邏輯,它會成為網狀結構中的中心樞紐及很多邏輯的起點。這種方法可能會導致少量的上帝服務,而與其打交道的那些服務通常都會淪為貧血的、基於CRUD的服務。

如果使用協同,可以僅僅從客戶服務中使用異步的方式觸發一個事件,該事件名可以叫做客戶創建。電子郵件服務、郵政服務及積分賬戶可以簡單的訂閱這些事件並且做相應的處理。這種方法能顯著的消除耦合。

缺點是,看不到圖4-2中展示的那種很明顯的業務流程視圖。

這意味着,需要做一些額外的工作來監控流程,以保證其正確的進行。處理該問題的一種方法是,構建一個與圖4-2中展示的業務流程相匹配的監控系統。實際的監控活動是針對每個服務的,但最終需要把監控的結果映射到業務流程中。在這個流程圖中我們可以看出系統是如何工作的。

通常來講,協同的方式可以降低系統的耦合度,並且你能更加靈活的對現有系統進行修改。但是,確實需要額外的工作來對業務流程做跨服務的監控。而且,大多數重量級的編排方案都非常不穩定且修改代價很大。基於這些事實,更推薦使用協同方式。因為在這種方式下,每個服務都足夠聰明,並且能夠很好的完成自己的任務。

如果想要請求/響應風格的語義,又想避免其在耗時業務上的困境,可以采用異步請求加回調的方式。另一方面,使用異步方式有利於協同方案的實施,從而大大減少服務之間的耦合,這恰恰就是我們為了能獨立發布服務而追求的特性。

針對請求/響應方式,可以考慮兩種技術:RPCRemote Procedure Call,遠程過程調用)和RESTRepresentational State Transfer,表述性狀態轉移)。

6. 遠程過程調用

遠程過程調用允許你進行一個本地調用,但事實上結果是由某個遠程服務器產生的。RPC的種類繁多,其中一些依賴於接口定義(SOAPThriftprotocol buffers)

不同的技術棧可以通過接口定義輕松的生成客戶端和服務端的樁代碼。

然而,所有這些技術都有一個核心特點,那就是使用本地調用的方式和遠程進行交互。

有些RPC實現與特定的網絡協議相綁定(比如SOAP名義上使用的就是HTTP),當然不同的實現會使用不同的協議,不同的協議可以提供不同的額外特性。比如TCP能夠保證送達,UDP雖然不能保證送達但協議開銷較小,所以你可以根據自己的使用場景來選擇不同的網絡技術。

那些RPC的實現會幫你生成服務端和客戶端樁代碼,從而讓你快速開始編程。這通常是RPC的主要賣點之一:易於使用。

但是有一些RPC的實現確實存在一些問題

6.1 技術的耦合

有一些RPC機制,如Java RMI,與特定的平台緊密綁定,這對於服務端和客戶端的技術選型造成了一定的限制。Thriftprotocol buffers對於不同的語言的支持很好,從而在一定程度上減小了這個問題的影響。

從某種程度上來講,這種技術上的耦合也是暴露內部實現細節的一種方式。

例如,使用RMI不僅把客戶端綁定在了JVM上,服務端也是如此。

6.2 本地調用和遠程調用並不相同

RPC的核心想法是隱藏遠程調用的復雜性。使用本地調用不會引起性能問題,但是RPC會花大量的時間對負荷進行封裝和解封裝,更別提網絡通信的時間。這意味着,要使用不同的思路來設計遠程和本地的API。只是簡單的把一個本地API改造成為跨服務的遠程API往往會帶來問題。

你還要考慮網絡本身。分布式計算中一個著名的錯誤觀點就是“網絡是可靠的”,事實上,網絡並不可靠。即使客戶端和服務端都正常運行,整個調用也有可能會出錯。你應該做出一個假設:有一個惡意的攻擊者隨時有可能對網絡進行破壞,因此網絡的出錯模式也不止一種。

6.3 脆弱性

有一些很流行的RPC實現可能會造成一些令人討厭的脆弱性。

這里以Java RMI為例。

如果對規格說明(指服務端定義的接口,比如添加了新的接口)進行了修改,所有的客戶端都需要重新生成樁(這里的樁應該是指的客戶端的方法實現),無論該客戶端是否需要這個新方法。

還有一種形式的脆弱性。如果我們對服務端中某個對象的字段做了修改(比如刪除了一個無用的字段),如果客戶端沒有做相應的修改的話,那么即使它們從來沒有使用過這個字段,在使用過程中還是會出問題。所以為了應用的這些修改,需要同時對服務端和客戶端進行部署。

這就是任何一個使用二進制樁生成機制的RPC所要面臨的挑戰:客戶端和服務端的部署無法分離。

6.4  RPC很糟糕嗎?

當然不是。

如果你決定要選用RPC這種方式的話,需要注意一些問題:不要對遠程調用過度抽象,以至於網絡因素完全被隱藏起來;確保你可以獨立的升級服務端接口而不用強迫客戶端升級,所以編寫客戶端代碼時要注意這方面的平衡;在客戶端中一定不要隱藏我們是在做網絡調用這個事實;在RPC的方式中經常會在客戶端使用庫,但是這些庫如果在結構上組織的不夠好,也可能會帶來一些問題。

6.5總結:

RPC是請求請求/響應協作方式的一種,相比使用數據庫做集成的方式,RPC顯然是一個巨大的進步。

7. REST

REST 是受Web啟發而產生的一種架構風格。REST風格包含了很多的原則和限制,但是這里我們僅僅專注於,如何在微服務的世界里使用REST更好的解決集成問題。REST是RPC的一種替代方案。

其中最重要的一點是資源的概念。資源,比如Customer,處於服務之內。服務可以根據請求內容創建Customer對象的不同表示形式。也就是說,一個資源的對外顯示方式和內部存儲方式之間沒有任何耦合。

REST本身並沒有提到底層應該使用什么協議,盡管事實上最常用HTTP。實際上,也有使用其他協議來實現REST的例子,比如串口或者USB,當然這會引入大量的工作。

7.1 REST和HTTP

事實上,REST架構風格聲明了一組對所有資源的標准方法,而HTTP恰好也定義了一組方法可供使用。

從概念上來說,對於一個Customer資源,訪問接口只有一個,但是可以通過HTTP協議的不同動詞對其進行不同的操作。

HTTP周邊也有一個大的生態系統,其中包含很多的支撐工具和技術。比如類似Varnish這樣的HTTP緩存代理、mod proxy這樣的負載均衡、大量針對HTTP的監控工具等。這些組件可以幫助我們很好的處理HTTP流量,並使用聰明的方式對其進行路由,而且這些操作基本上都對終端客戶透明。HTTP還提供了一系列安全控制機制供我們直接使用。從基本認證到客戶端證書,HTTP生態系統提供了大量的工具來簡化安全性處理。

需要注意的是,HTTP也可以用來實現RPC。比如SOAP就是基於HTTP進行路由的,但不幸的是它只用到HTTP很少的特性。

7.2 超媒體作為程序狀態的引擎

REST引入的用來避免客戶端和服務端之間產生耦合的另一個原則是“HATEOAS”(Hypermedia As The Engine of Application State,超媒體作為程序狀態的引擎。)

超媒體的概念是:有一塊內容,該內容包含了指向其他內容的鏈接,而這些內容的格式可以不同(如文本,圖像,聲音等)。

HATEOAS背后的想法是,客戶端應該與服務端通過那些指向其他資源的鏈接進行交互,而這些交互有可能造成狀態轉移。

這就類似於網頁下的帶有超鏈接的圖標,例如,你想加入購物車,你只需要去點擊圖標,圖標下會有一個超鏈接來完成加入的動作。

7.3 JSON、XML還是其他

由於服務端使用標准文本形式的響應,所以客戶端可以很靈活的對資源進行使用,而基於HTTP的REST能夠提供多種不同的響應形式。例如XML,或者更流行的JSON。

7.4 留心過多的約定

由於REST越來越流行,幫助我們構建RESTFul Web 服務的框架也隨之流行起來。

我們很容易把存儲的數據直接暴露給消費者,那么如何避免這個問題呢?(這種方式內在耦合性所帶來的痛苦會遠遠大於一開始就消除概念之間的耦合所需要的代價。)

一個有效的模式是先設計外部接口,等到外部接口穩定之后在實現微服務內部的數據持久化。

這樣做可以保證服務的接口是由消費者的需求驅動出來的,從而避免數據存儲方式對外部接口的影響。其缺點是推遲了數據存儲部分的集成。

7.5 基於HTTP的REST的缺點

從易用性的角度來看,基於HTTP的REST無法幫助你生成客戶端的樁代碼,而RPC可以

另外,性能上也可能會遇到問題。基於HTTP的REST支持不同的格式,比如JSON或者二進制,所以負載相對SOAP來說更緊湊,當然和像Thrift這樣的二進制協議是沒法比的。在要求低延遲的場景下,每個HTTP請求的封裝開銷可能是個問題。

雖然HTTP可以用於大流量的通信場景但對於低延遲通信來說並不是最好的選擇。相比之下,有一些構建與TCPTransmission Control Protocol,傳輸控制協議)或者其他網絡技術之上的協議更加高效。比如WebSockets,在初始的HTTP握手之后,客戶端和服務器之間就僅僅通過TCP連接了。對於向瀏覽器傳輸數據這個場景而言,WebSockets更加高效。

對於服務和服務之間的通信來說,如果低延遲或者較小的消息尺寸對你來說是很重要的話,那么一般來講HTTP不是一個好主意。你可能需要選擇一個不同的底層協議,比如UDPUser Datagram Protocol, 用戶數據報協議)來滿足你的性能要求。很多RPC框架都可以很好的運行在除了TCP之外的其他網絡協議上。

有些RPC的實現支持高級的序列化和反序列化機制,然而對於REST而言,這部分工作就要自己做了。

盡管有這些缺點,在選擇服務之間的交互方式時,基於HTTP的REST仍然是一個比較合理的默認選擇。

 

未完,待續...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM