技術(shù)上解耦的手段:集成
1. 理想的集成技術(shù)
1) 避免破壞性修改:修改一個服務(wù)不會導(dǎo)致該服務(wù)的消費方隨之發(fā)生改變。如:一個微服務(wù)在響應(yīng)中添加一個字段,已有的消費方不會受到影響
2) 保證api的技術(shù)無關(guān)性:對微服務(wù)的實現(xiàn)沒有限制
3) 易于消費方使用:消費方可以用任何技術(shù)實現(xiàn)。
4) 隱藏內(nèi)部實現(xiàn)細節(jié):服務(wù)內(nèi)部實現(xiàn)細節(jié)對消費方不可見
2. 現(xiàn)在常用的集成技術(shù):
集成即是為用戶創(chuàng)建接口
1) 共享數(shù)據(jù)庫:業(yè)界最常用的方式
a. 優(yōu)點:
i. 直接操作數(shù)據(jù)庫,最初的實現(xiàn)簡單、快速
ii. 共享數(shù)據(jù)簡單
b. 劣勢:
i. 不同的上下文通過數(shù)據(jù)庫耦合在一起;
ii. 內(nèi)部暴露給消費者;
iii. 很難做到無破壞性修改,進而不可避免地不做任何修改
iv. 消費方與特定的技術(shù)綁在了一起;
v. 無法共享行為
2) 同步:發(fā)起一個遠程服務(wù)調(diào)用后,調(diào)用方會阻塞自己到整個操作的完成。
同步可以使用的協(xié)作方式:請求/響應(yīng)
a. 優(yōu)勢:
i. 可以知道調(diào)用成功與否
ii. 技術(shù)實現(xiàn)簡單
b. 劣勢:
i. 運行時間長的應(yīng)用,需要客戶端和服務(wù)器之間的長連接
ii. 高延遲
3) 異步:調(diào)用方不需要等待操作是否完成就可以返回,甚至可能不關(guān)心操作完成與否
可以使用的協(xié)作方式:請求/響應(yīng)或者基于事件
a. 優(yōu)勢:
i. 對運行比較長的任務(wù)比較有用,否則客戶端和服務(wù)器之間要開啟長連接。
ii. 低延遲
iii. 基于事件的協(xié)作方式可以分布處理邏輯,低耦合
b. 劣勢:
i. 處理異步通信的技術(shù)相對復(fù)雜
4) 編排(orchestration):使用某個中心大腦來指導(dǎo)并驅(qū)動整個流程,可以用管弦樂隊的指揮來比喻
a. 優(yōu)勢:
i. 調(diào)用簡單
ii. 客戶服務(wù)本身可以對跨服務(wù)業(yè)務(wù)流程進行到哪一步進行跟蹤
iii. 有工具幫助實現(xiàn),比如一個合適的規(guī)則引擎,流程建模軟件
iv. 如果使用的是同步的請求/響應(yīng)模式,甚至能知道每一步是否成功
b. 劣勢:
i. 作為中心控制點的客戶服務(wù)會成為瓶頸:網(wǎng)狀結(jié)構(gòu)的中心樞紐及很多邏輯的起點
ii. 其它的服務(wù)通常都會淪為貧血的、基于crud的服務(wù)
iii. 重量級的編排方案都非常不穩(wěn)定并且修改代價極大
5) 協(xié)同(choreography):告知系統(tǒng)中各個部分各自的職責(zé),具體怎么做的細節(jié)留給自己,可以用芭蕾舞中的每個舞者來做比喻,通常使用事件驅(qū)動的方式
a. 優(yōu)勢:
i. 顯著地消除耦合
b. 劣勢:
i. 看不到業(yè)務(wù)流程的進展
ii. 需要額外的工作來監(jiān)控跨服務(wù)的流程,以保證其正確的進行。實際的監(jiān)控活動是針對每個服務(wù)的,但最終把監(jiān)控的結(jié)果映射到業(yè)務(wù)流程中
相對于編排,在微服務(wù)架構(gòu)里優(yōu)先選擇協(xié)同
3. 請求/響應(yīng)模式相關(guān)的技術(shù)
同步或異步的協(xié)作方式都可以用到請求/響應(yīng)模式,請求/響應(yīng)模式有兩個典型的技術(shù):
1) 遠程過程調(diào)用RPC:本地調(diào)用,遠程服務(wù)器執(zhí)行并返回結(jié)果
RPC類型:
a. 依賴于接口的,如SOAP,Thrift,及protocol buffers等。不同的技術(shù)棧可以通過接口定義輕松生成客戶端和服務(wù)器端的樁代碼。
例如:Java服務(wù)暴露一個SOAP接口,然后使用WSDL(Web Service Definition Language,Web服務(wù)描述語言)定義的接口生成.Net客戶端的代碼;
b. 其它的技術(shù),不需要額外的共享接口定義,但是會導(dǎo)致服務(wù)端和客戶端之間更緊的耦合。如Java RMI。
RPC的優(yōu)勢:
a. 易于使用:只使用普通的接口調(diào)用方式,而不用關(guān)注實現(xiàn)的細節(jié)。依賴于接口的RPC也會生成大量的樁代碼
RPC的劣勢:
a. 技術(shù)上的耦合:有一些RPC機制,如Java RMI,與特定的平臺緊密綁定,對技術(shù)選型有限制。
這種技術(shù)的耦合也暴露了內(nèi)部的細節(jié)
b. 對遠程和本地的API,使用的思路不同,遠程的要考慮對符合進行封裝和解封裝。
i. 簡單地把本地api改裝成跨服務(wù)的遠程api會帶來問題
ii. 開發(fā)人員很多時候也不知調(diào)用的是本地還是遠程api
iii. 網(wǎng)絡(luò)的不可靠性:即使客戶端和服務(wù)器工作正常,調(diào)用也可能會出錯。
c. 脆弱性:使用二進制樁生成機制的rpc(例如RMI)所普遍面臨的挑戰(zhàn),當(dāng)服務(wù)器端的數(shù)據(jù)類型里的部分字段被刪除、新增字段或者修改字段,遠程的客戶端都要隨之一起變化。如果遠程的客戶端不能一起調(diào)整,就會出錯。這就帶來了脆弱性。
更現(xiàn)代的RPC,如protocol buffers或者Thrift,會通過避免對客戶端和服務(wù)端的lock-step發(fā)布(具體如何做的?)來消除脆弱性。
d. 沒有充分應(yīng)用HTTP:SOAP是基于HTTP進行路由的,但不幸的是它僅用到HTTP很少的特性,動詞及HTTP的錯誤碼都被忽略了,HTTP的潛力沒有得到充分應(yīng)用。
2) 基于HTTP的REST:RPC的一種替代方案。
最重要的概念是資源,服務(wù)可以根據(jù)請求內(nèi)容創(chuàng)建資源的不同表現(xiàn)形式。
REST的優(yōu)勢:
a. 資源的解耦:資源的對外顯示方式和內(nèi)部存儲方式之間沒有什么耦合。例如:客戶端可能會請求一個Customer 的JSON表示形式,而Customer 的內(nèi)部存儲方式可以不同。
b. HTTP的動詞(GET、POST和PUT)和REST的資源一起使用,意味著可以只用資源(名詞)作為入口,用動詞進行操作:
i. 對于一個資源,接口只有一個,但可以通過HTTP協(xié)議的不同動詞對其進行不同的操作。
例如,對Customer 資源的操作,避免了很多版本的createCustomer及editCustomer的方法,想法
c. 可以利用HTTP周邊的大的生態(tài)系統(tǒng)。比如Varnish這樣的HTTP緩存代理;mod_proxyzh這樣的負載均衡器;HTTP的監(jiān)控工具;HTTP的安全控制機制
d. 引入HATEOAS(Hypermedia As The Engine Of Application State,超媒體作為程序狀態(tài)的引擎)原則,避免客戶端和服務(wù)端之間的耦合
HATEOAS背后的原理:客戶端和服務(wù)端通過指向其它資源的鏈接進行交互。通過HATEOAS,不需要一再調(diào)整客戶端代碼來匹配服務(wù)器端的改變。通過這些鏈接,客戶端能自行獲取相關(guān)API。
REST的劣勢:
a. 使用HATEOAS時,客戶端和服務(wù)端之間的通信會過多,因為客戶端會不斷發(fā)送鏈接、請求,直到找到自己想要的那個操作。
優(yōu)化方案:讓客戶端自行遍歷和發(fā)現(xiàn)api
b. 基于HTTP的REST無法幫助生成客戶端的樁代碼,要在客戶端自己實現(xiàn)
容易犯的錯是構(gòu)建出一些共享庫,在客戶端和服務(wù)器端之間共享。這樣就引起緊耦合的問題。
c. 有些Web 框架無法很好支持所有的HTTP動詞。GET和POST請求都支持(等待驗證),但是有的框架不能支持PUT和DELETE。
d. 不能很好地滿足高性能和低延遲的要求:REST的JSON或者二進制,相對SOAP來說更加緊湊,每個HTTP請求的封裝開銷是個問題。
解決方案:用WebSockets替代HTTP,WebSockets更加高效。
4. 基于事件的異步協(xié)作方式
微服務(wù)架構(gòu)通常選用事件驅(qū)動的方式。使用事件驅(qū)動的注意事項:
1) 確保各個流程有很好的監(jiān)控機制
2) 考慮關(guān)聯(lián)ID,可以對跨進程的請求進行跟蹤
基于事件的異步協(xié)作方式有兩種:
1) 消息代理
服務(wù)生產(chǎn)者使用API向代理發(fā)布事件,代理向服務(wù)消費者提供訂閱服務(wù),并在事件發(fā)生時通知消費者。
優(yōu)勢:
a. 能處理微服務(wù)發(fā)布事件機制及消費者接收事件機制的問題
b. 可以跟蹤消費者的狀態(tài),比如標記哪些消息是該消費者已經(jīng)消費過的了。
c. 具有較好的可伸縮性和彈性
劣勢:
a. 增加開發(fā)流程的復(fù)雜度:
響應(yīng)返回時如何處理?是否發(fā)到請求的那個節(jié)點?如果是,節(jié)點停止工作了怎么辦?如果不是,是否需要把消息先存儲起來再處理?
b. 需要一個額外的系統(tǒng)(即消息代理)才能開發(fā)和測試服務(wù),需要額外的機器和專業(yè)知識
c. 消息代理是中間件的一部分,但是很多中間件廠家通常傾向于把很多的軟件打包進去,比如企業(yè)服務(wù)總線(ESB)。
規(guī)避方式:盡量讓中間件保持簡單,而且把業(yè)務(wù)邏輯放到自己的服務(wù)中。
2) ATOM:一種符合REST規(guī)范的協(xié)議,通過它提供資源聚合(feed)的發(fā)布服務(wù)
優(yōu)勢:
a. 使用方便,很多現(xiàn)成的客戶端庫可以消費該聚合。客戶服務(wù)發(fā)生改變時,只需簡單向該聚合發(fā)布一個事件即可。
b. HTTP能很好處理伸縮行
劣勢:
a. HTTP不擅長處理低延遲
b. 用戶需要自己跟蹤消息是否送達及管理輪詢等工作。
c. 消費者競爭關(guān)系:用戶需要到資源聚合里輪詢,很多用戶消費一個資源就會引起競爭關(guān)系。
5. 用戶界面作為集成的組合層
用戶界面支持服務(wù)之間的集成,連接各個微服務(wù)的工具
1) 約束:
a. 桌面網(wǎng)頁:
i. 用戶瀏覽器
ii. 屏幕解析度
b. 移動端:
i. 帶寬
ii. 電池電量的消耗
iii. 單手操作
iv. 短信交互
2) API組合
a. 使用多個API來表示用戶界面:UI主動訪問所有API,然后再將狀態(tài)同步到UI控件
問題:
i. 很難為不同的設(shè)備定制不同的響應(yīng)
建議方案:允許客戶指定它想要哪些字段,但這就需要每個服務(wù)都支持這種方式。
ii. 誰來創(chuàng)建用戶界面?另一個團隊會退回到分層合作方式。
b. UI片段的組合:服務(wù)直接暴露出一部分UI,然后只需簡單地把這些片段組合在一起就可以創(chuàng)建出整體UI
i. 優(yōu)勢:服務(wù)團隊可以同時維護這些UI片段
ii. 問題:
1) 保證無縫的用戶體驗:可以利用活樣式指導(dǎo)解決
2) 原生應(yīng)用和胖客戶無法消費服務(wù)端提供的UI組件。解決方案:嵌入HTML插件
3) 界面的動態(tài)刷新是否可以做到?例如搜索時,鍵入關(guān)鍵字推薦信息自動刷新
c. 為前端服務(wù)的后端:適用于1)與后端交互比較頻繁的界面;2)需要給不同設(shè)備提供不同內(nèi)容的界面
解決方案:API入口
i. 優(yōu)勢:
1) API入口可以對多個后端調(diào)用進行編排;
2) 對多個后端調(diào)用進行編排
3) 為不同設(shè)備提供定制化內(nèi)容
ii. 劣勢:
1) API入口如果包含邏輯太多,就會難以維護
2) 失去不同用戶界面之間的隔離性
3) 限制了獨立于彼此進行發(fā)布的能力
iii. 建議:專用后端,一個后端只為一個應(yīng)用或者用戶界面服務(wù),也叫做BFF(Backends For Frontends,為前端服務(wù)的后端)
API認證和授權(quán)可以處于BFF和UI之間
BFF的風(fēng)險:包含不該包含的邏輯;業(yè)務(wù)邏輯應(yīng)該在服務(wù)中,不應(yīng)該在BFF里;BFF應(yīng)該僅僅包含與實現(xiàn)某種特定的用戶體驗相關(guān)的邏輯
6. 集成的其它原則
1) 服務(wù)即狀態(tài)機:
a. 微服務(wù)應(yīng)該擁有所屬限界上下文中行為相關(guān)的所有邏輯;
b. 客戶服務(wù)控制所有與客戶生命周期相關(guān)的事件。
c. 把關(guān)鍵領(lǐng)域的生命周期顯式建模,作為唯一一個處理狀態(tài)沖突的地方,并可以在這些狀態(tài)變化的基礎(chǔ)上封裝一些行為。
2) 響應(yīng)式擴展(Reactive extensions,Rx):
a. 可以把多個調(diào)用(阻塞/非阻塞)的結(jié)果組裝起來,
b. 一些Rx實現(xiàn)對組裝后的結(jié)果進行某種函數(shù)變換。如RxJava中就可以使用類似map或者fileter的經(jīng)典函數(shù)
c. 需要做一些基于多個服務(wù)調(diào)用的操作時,使用微服務(wù)所選用的技術(shù)棧的響應(yīng)式擴展。
3) DRY:避免系統(tǒng)行為和知識的重復(fù)
優(yōu)勢:
可以得到重用性比較好的代碼,把重復(fù)代碼抽取出來,然后在多個地方進行調(diào)用
劣勢:
微服務(wù)和消費者之間的過度耦合,一處修改,處處調(diào)整
4) 資源的有效性:在處理請求的過程中,資源有可能會發(fā)生改變。
a. 按引用訪問:發(fā)送資源的URI,而不是資源的具體信息,等請求處理完成后再查詢URI
b. 基于事件協(xié)同的對位:資源發(fā)生變化后,發(fā)送事件通知,同時使用資源的URI
c. 提供資源的有效性時限信息:獲取資源的同時,獲取資源的有效性時限(即資源在什么時間之前是有效的)。可以利用HTTP的緩存控制
5) 版本管理
服務(wù)的接口難免會發(fā)送改變,通過版本進行管理。
a. 盡可能推遲破壞性修改:
i. REST對于內(nèi)部的修改不大會引起服務(wù)接口的變化
ii. 避免將客戶端和服務(wù)端緊密綁定:如服務(wù)端使用JSON發(fā)送響應(yīng),客戶端利用XPath提取所需的信息(Martin ?Fowler稱其為容錯性讀取器)。
iii. Postel法則:寬進嚴出,對自己發(fā)送的東西要嚴格,對自己接收的東西要寬容
b. 及早發(fā)現(xiàn)破壞性修改
i. 使用消費者驅(qū)動的契約來及早定位
c. 使用語義化的版本管理
MAJOR.MINOR.PATCH
i. MAJOR:包含不向后兼容的修改
ii. MINOR:有新功能的增加,但是是向后兼容的
iii. PATCH:對已有功能的缺陷修復(fù)
d. 不同的接口共存
i. 新接口和老接口同時存在:在發(fā)布一個破壞性修改時,同時部署一個包含新老接口的版本
ii. 需要對不同的請求進行路由:HTTP的系統(tǒng),把版本信息添加到URI中;
e. 同時使用多個版本的服務(wù)
i. 同時運行不同版本的服務(wù):老用戶路由到老版本的服務(wù),新用戶路由到新版本的服務(wù)。
ii. 藍綠部署或者金絲雀發(fā)布
6) 和第三方軟件的集成
這里第三方軟件特指不受控制的系統(tǒng),例如COTS或SaaS
a. 在自己可控的平臺進行定制化:任何定制化只在自己可控的平臺上進行,并限制工具的消費者的數(shù)量
b. 絞殺者模式:捕獲并攔截對老系統(tǒng)的調(diào)用,1)路由到現(xiàn)存的遺留代碼;2)路由到新寫的代碼
通常使用一系列的微服務(wù)來攔截,而不是一個單一的單塊應(yīng)用。這時攔截并重定向會變得很復(fù)雜,可能要引入代理模式。