1. 程式人生 > >微服務之整合(四)

微服務之整合(四)

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中展示的業務流程相匹配的監控系統。實際的監控活動是針對每個服務的,但最終需要把監控的結果對映到業務流程中。在這個流程圖中我們可以看出系統是如何工作的。

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

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

針對請求/響應方式,可以考慮兩種技術:RPC(Remote Procedure Call,遠端過程呼叫)和REST(Representational State Transfer,表述性狀態轉移)。

6. 遠端過程呼叫

遠端過程呼叫允許你進行一個本地呼叫,但事實上結果是由某個遠端伺服器產生的。RPC的種類繁多,其中一些依賴於介面定義(SOAP、Thrift、protocol buffers等)。

不同的技術棧可以通過介面定義輕鬆的生成客戶端和服務端的樁程式碼。

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

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

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

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

6.1 技術的耦合

有一些RPC機制,如Java RMI,與特定的平臺緊密繫結,這對於服務端和客戶端的技術選型造成了一定的限制。Thrift和protocol 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可以用於大流量的通訊場景,但對於低延遲通訊來說並不是最好的選擇。相比之下,有一些構建與TCP(Transmission Control Protocol,傳輸控制協議)或者其他網路技術之上的協議更加高效。比如WebSockets,在初始的HTTP握手之後,客戶端和伺服器之間就僅僅通過TCP連線了。對於向瀏覽器傳輸資料這個場景而言,WebSockets更加高效。

對於服務和服務之間的通訊來說,如果低延遲或者較小的訊息尺寸對你來說是很重要的話,那麼一般來講HTTP不是一個好主意。你可能需要選擇一個不同的底層協議,比如UDP(User Datagram Protocol, 使用者資料報協議)來滿足你的效能要求。很多RPC框架都可以很好的執行在除了TCP之外的其他網路協議上。

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

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

 

未完,待