第四章:集成
集成是微服務相關技術中最重要的一個。做得好的話,你的微服務可以保持自治性,可以獨立修改和發布他們,如果做的不好的話,會帶來災難。 4.1尋找理想的集成技術 微服務間的通訊選擇性很多,REST、SOAP、RPC、Protocol buffers等。 4.11避免破壞性修改 有些時候對一個微服務的修改會造成該服務消費者的修改,例如:微服務A增加了一個字段,如果處理不好的話,會導致A服務的消費者B服務也必須增加該字段才能保證服務B能夠正常調用A服務。 4.12保證API的技術無關性 保證微服務間的技術無關性非常重要,這意味著不應該選擇那種對微服務的具體實現技術有限制的集成方式。 4.13使你的服務易于消費方使用 消費方應該很容易的調用我們的服務。理想情況下,消費方可以使用任何技術來實現; 另一方面,提供一個客戶端庫也可以簡化消費方的使用,但是消費方使用客戶端庫也會造成微服務間的耦合。 4.14隱藏內部實現細節 我們不希望消費方和服務的內部實現細節綁定在一起,因為這會增加耦合。 與細節綁定意味著,如果改動服務內部的一些實現,消費方就需要跟著做出修改,這會增加成本,因此我們要避免。 這也會導致我們為了避免消費方的修改而盡量少的對服務本身進行修改,從而導致服務內部技術債的增加。 因此,所有傾向于暴露內部實現細節的技術都不應該被采用。 4.2為用戶創建接口 以MusicCrop為例,創建客戶這個業務,包括新客戶的創建、付賬設置、發送歡迎郵件等接口; 接下來會以這個業務為例說明集成的各種方式。 4.3共享數據庫 業界最常見的集成形式就是共享數據。使用這種方式時,如果其他服務想要從一個服務獲取信息,可以直接訪問數據庫。如果想要修改,也可以直接在數據庫中修改。這種方式看起來非常簡單,而且可能是最快的集成方式,這也正是它流行的原因。 共享數據庫的方式有以下缺點: 多個服務同時訪問相同共享數據庫,會造成外部服務能夠查看到內部實現細節,并與其綁定在一起。存儲在數據庫中的數據對所有人來說都是公平的,所有服務都可以完全訪問該數據庫。如果我們更改數據庫表結構,那么消費方就無法進行工作。共享數據庫是一個大的共享API,但同時也非常不穩定。 消費方與特定的技術進行了綁定,這說的是所有服務都與共享數據庫進行了綁定。如果一個某個服務使用非關系型數據庫更好,那么會為以后這種形式的修改帶來很大的困難。這也造成了服務間的耦合。 微服務的設計核心:高內聚和松耦合。因此通過共享數據庫的方式很難滿足設計要求。 4.4同步和異步 服務間的協作包括兩種方式“同步和異步。 如果使用同步通信,發起一個遠程服務調用后,調用方會阻塞自己并等到整個操作的完成。 如果使用異步通信,調用方不用等待操作完成就可以返回,甚至不需要關心這個操作是否完成。 同步可以知道事情是否成功執行。 異步通信對于運行時間比較長的任務來說比較有用,否則就需要在客戶端和服務端之間開啟一個長連接,這是非常不實際的。 當需要低延遲的時候可以使用異步通信。 處理異常通信的技術相對來說比較復雜。 同步通信 即請求/響應:發起一個請求,然后等待響應。 異步通信 即基于事件:發起一個請求,注冊一個回調,當服務端操作結束后,會調用該回調。 對于使用基于事件的協作方式來說,客戶端不是發起請求,而是發布一個事件,然后期待其他的協作者接收到該消息,并知道該怎么做。 基于事件的系統天生就是異步的; 業務邏輯并非集成中某個核心服務中,而是平均分布在不同的協作者中。 基于事件的協作方式耦合性很低。 4.5編排和協作 對復雜業務邏輯進行建模時,我們需要處理跨服務業務流程的問題,而使用微服務時這個問題會來的更快,我們以MusicCrop中創建用戶為例,看看創建用戶業務時會發生什么。 MusicCrop的創建用戶業務包括三個子業務: 在客戶的積分賬號中創建一條記錄; 通過郵政系統給用戶發送一個歡迎禮包; 通過郵件系統發送一條歡迎郵件; 當考慮具體實現時,有兩種架構風格可以采用: 一種是使用編排,我們依賴某個中心大腦來驅動整個流程。 編排的話,在創建客戶時,會通過請求/響應的方式進行通訊,我們可以對當前進行到具體哪一個進行跟蹤。 編排的缺點是客戶服務作為中心控制點承擔了太多職責,它會成為網狀結構的中心樞紐及很多邏輯的起點。例如客戶服務中心可能會成為“上帝服務”,而其他三個子服務會成為“貧血服務”即只是簡單的CRUD操作。 大多數的重量級的編排方案都非常不穩定,而且修改的代價很大。 一種是使用協同,我們僅僅需要告訴系統中各個部分各自的職責,而把具體怎么做的細節留給它們自己。 使用協同,可以僅僅從客戶中心服務使用異步的方式觸發一個事件,該事件名可以叫做“創建客戶”,例如可以使用訂閱或者消息系統,客戶服務中心發送一個消息,然后其他三個子服務自己去執行各自的服務。 使用協同可以消除耦合,同時可以對現有系統更加靈活的修改。 協同的缺點是看不到具體的創建客戶業務流程處理到哪一個步驟,比較難以跟蹤。 同時意味著我們需要做一個些額外的監控工作,以保證其正確的進行。 基于以上兩種方式的比較,我更傾向于使用協同的方式,在這種方式下每個服務都足夠聰明,并且能夠很好的完成自己的任務。 這里有好幾個因素需要考慮:同步調用比較簡單,而且容易知道整個流程的工作是否正常,如果想用使用請求/響應風格的語義,可以使用異步請求加回調的方式;另一方面,使用異步調用的方式有利于協同方案的實施,從而大大減少服務間的耦合,這恰恰就是我們為了能夠獨立發布服務而追求的特定。 針對請求/響應可以考慮兩種技術RPC和REST。 4.6遠程過程調用RPC 遠程過程調動允許你進行一個本地調用,但是事實上結果是由某個遠程服務器產生的。 RPC的實現會幫你生成服務端和客戶端的樁代碼,從而讓你快速開始編碼。基本上不用花時間,我們就可以再服務間進行內容交互了。 然而有一些RPC的實現確實存在一些問題。這些問題通常一開始不明顯,但慢慢地就會暴露出來,并且帶來的代價要遠遠大于一開始快速啟動的好處。 4.6.1技術的耦合 有一些RPC機制,如Java RMI與特定的平臺緊密綁定,這對于服務端和客戶端的技術選型造成了一定限制。Thrift和protocol buffers對于不同語言的支持很好,從而在一定程度上減少了這個問題的影響。 從某種程度上來說,這種技術上的耦合也會暴露內部實現細節的一定方式。例如使用Java RMI不僅把客戶端綁定在JVM上,服務端也是如此。 4.6.2本地調用和遠程調用并不相同 RPC的和興想法是隱藏遠程調用的復雜性。但是很多RPC的實現隱藏的有些過頭了,進而會造成一些問題。使用本地調用不會引起性能問題,但是RPC會花大量的時間對負荷進行封裝和解封裝,更別提網絡通信需要的時間。 你需要考慮網絡本身的問題。分布式計算中一個非常著名的錯誤觀點就是“網絡是可靠的”,事實上網絡并不可靠。及時客戶端和服務端都正常運行,整個調用也有可能出錯,這些錯誤有可能會很快發生,有可能過一段時間才會顯現出來,它們升值有可能損壞你的報文,因此網絡的出錯模式也不止一種。 4.6.3.脆弱性 有一些很流程的RPC實現可能會造成一些令人討厭的脆弱性,Java的RMI就是一個很好的例子,考慮一個非常簡單的接口,通過該接口可以向客戶服務發起一個遠程調用,如果對該接口進行了修改,由于是本地的樁代碼,你很容易把這個接口當成了本地接口進行了修改,從而造成錯誤。并且如果想修改一個接口,需要重新生成服務端和客戶端兩邊的樁代碼,這種修改會很頻繁。 這就是任何一個使用二進制樁代碼生成機制的RPC所要面臨的挑戰:客戶端和服務端的部署無法分離。如果使用這種技術。離lock-setp發布就不遠了。 實踐中,通訊雙方使用的數據類型會直接被序列化和反序列化,而如果數據類型中包含了大量的字段,這就會導致不再使用的字段無法被安全的刪除掉。 4.6.4RPC很糟糕嗎 盡管RPC存在這些缺點,但是可以通過使用thrift或者protocol buffers等技術來避免對客戶端和服務端的lock-step發布來消除上面提到的一些問題。 如果你決定使用RPC方式的話,需要注意一些問題: 不要對遠程調用過度抽象,以至于網路因素完全被隱藏起來; 確保你可以單獨的升級服務端的接口而不用強迫客戶端升級,所以在編寫客戶端代碼時要注意這個方面的均衡; 在客戶端中一定不要隱藏我們是在做網絡調用這個事實, 在RPC的方式中經常會在客戶端能使用庫,但是這些庫如果在結構上組織的不好,也有可能會帶來一些問題。 RPC是請求/響應協作方式的一種,相比使用數據庫做集成的方式,RPC顯然是一個巨大的進步。 4.7REST REST是受Web啟發而產生的一種架構風格,REST風格包含了需要原則和限制,但是這里我們僅僅專注于如果在微服務的世界使用REST更好的解決集成問題。 REST是RPC的一種替代方案。 REST最重要的一點就是資源的概念。服務可以根據請求的內容創建對象的不同表示形式。也就是說一個資源的對外顯示方式和內部存儲方式之間沒有什么耦合。 REST本身并沒有提到底層應該使用什么協議,盡管事實上最常用的http。http的一些特定,比如動詞put、get等,使得在http上實現REST要簡單的多,而如果使用其他協議的話,就需要自己實現這些特定。 4.7.1REST和HTTP HTTP本身提供了很多功能,這些功能對于實現REST風格非常有用,比如動詞put、get等。GET方式使用冪等的方式獲得資源。POST創建一個新資源。 HTTP周邊也有一個大的生態系統,其中包含很多支撐工具和技術,例如Varnish這要的HTTP緩存代理、mod_proxy這樣的負載均衡器。這些工具可以很好幫忙我們處理HTTP流量,并使用聰明的方式對其進行路由。而且這些操作劇本上都對終端用戶透明。HTTP還提供了一系列安全控制機制供我們直接使用。包括認證證書。 需要注意的是HTTP也可以用來實現RPC,比如SOAP就是具有HTTP進行路由的。 4.7.2超媒體作為程序狀態的引擎 REST引入的用來避免客戶端和服務端直接產生耦合的另一個原則是“超媒體作為程序狀態的引擎”。 4.7.3JSON、XML還是其他 由于服務端使用標準文本形式的響應,所以客戶端可以很靈活的對資源進行使用,而基于HTTP和REST能夠提供多種不同的響應形式。例如XML和JSON。 JSON無論從形式上還是使用方法上來說都更加簡單。相對于XML來說JSON更加緊湊。 JSON也有一些缺點,XML使用連接來進行超媒體控制,JONS中并沒有類似的東西。 當然并不是說只有兩種格式,通過HTTP我們可以發送任何格式,甚至于二進制的。 4.7.4留心過多的約定 REST越來越流行,但是有一些工具會為了短期利益而犧牲長期利益。例如有些框架可以很容易的表示數據庫對象,并把它們反序列化成進程內的對象,然后直接暴露給外部。這種方式內在的耦合性所帶來的痛苦會遠遠大于從一開始就消除該你啊你之間的耦合所需要的代價。那如何這個問題那?一個有效的方式是先進行外部接口的設計,等到外部接口穩定之后再實現微服務內部的數據持久化,這樣可以保證接口是由消費者的需求驅動出來的,從而避免數據春方式對外部接口的影響。 4.7.5基于HTTP的REST的缺點 從易用性的角度來看,基于HTTP的REST無法幫助你生成客戶端的樁代碼,而RPC可以。 使用HTTP意味著可以使用很多的HTTP客戶端。 從個人的角度來看,使用客戶端的庫可以做的更好,但是使用庫會增加復雜度,因為人們會不自覺的回到基于HTTP的RPC的思路上來,然后構建出一些共享庫。在客戶端和服務端之間共享代碼是非常危險的。 還有一個問題就是有些Web框架無法很好的支持所有的HTTP動詞,可能很容易處理GET和POST,但是PUT和DELETE就比較麻煩了。推薦使用Jersey這樣比較好的REST框架就不存在這樣的問題。 性能上也可能會遇到問題,基于HTTP的REST支持不同的格式。比如JSON或者二進制,所以負載相對SOAP來說更加緊湊,當然和像Thrift這樣的二進制協議是沒辦法比的,在低延遲的場景下,每個HTTP請求的封裝開銷可能是個問題。 雖然HTTP可以用于大流量的通訊場景,但是對于低延遲通訊來說并不是最好的選擇,相比之下,有一些構建于TCP或者其他網絡技術之上的協議更加高效。比如WebService。 對于服務和服務之間的通訊來說,如果低延遲或者較小的消息尺寸對你來說很重要的話,那么一般來講HTTP不是一個好主意,你可以選擇一個不同的底層協議,類似UDP來滿足你的性能要求,很多RPC框架都是可以很好地運行在除了TCP之外的其他網絡協議上。 有些RPC的實現支持高級的序列化和反序列化支持,然而對于REST來說,這部分工作就要自己實現了。這部分工作可能會成為服務端和客戶端之間的一個耦合點,因為實現一個具有容錯性的讀取器不是一件容易的事情。 盡管有些缺點,在選擇服務間的交互方式時,基于HTTP的REST仍然是一個比較合理的默認選擇。 4.8實現基于事件的異步協作 前面討論了基于請求/響應相關的技術,接下來介紹基于異步事件的通訊。 4.8.1技術選擇 主要有兩個部分需要考慮:微服務發布事件機制和消費者接收事件機制。 傳統來說,像RabbitMQ這樣的消息代理能夠處理上述兩方面的問題。生產者使用API向代理發布事件,代理可以向消費者提供訂閱服務,并且在事件發布的時候通知消費者。 不過需要注意的是消息代理僅僅是中間件世界中的一小部分而已。隊列本身是很合理、很有用的東西。需要謹記一個原則:盡量讓中間件保持簡單,而把業務邏輯放在自己的服務中。 另一種方法時使用HTTP來傳播事件。ATOM是一個符合REST規范的協議,可以通過他提供資源聚合的發布服務,而且有很多現成的客戶端可以用來消費該聚合。另一方面ATOM規范和與之相關的庫用起來非常便捷,而且HTTP能夠很好的處理伸縮性。但HTTP不擅長處理低延遲的場景,而且使用ATOM的話,用戶還需要自己追蹤消息是否送達及管理輪詢等工作。 4.8.2異步架構的復雜性 事件驅動機制的系統看起來耦合非常低,而且伸縮性很好,但是這樣的編程風格會帶來一定的復雜性,這種復雜性并不僅僅包括對消息的發布訂閱操作。例如一個非常耗時的異步請求/響應,需要考慮響應返回時需要怎么處理,該響應是否回到發送請求的那個節點?如果是的話,節點服務停止了怎么辦?如果不是的話,是否需要把信息實現存儲到某個比其他地方,以便于做相應的處理?如果API設計的好的話娿,短生命周期的異步操作還是比較容易管理的,但盡管如此,對于習慣了進程間同步使用的程序員來說,使用異步模式也需要思維上的轉換。 需要考慮: 限制重試次數; 所有失敗的消息需要存儲在一個失敗隊列中。 需要界面展現失敗隊列中的消息; · 事件驅動架構和異步編程會帶來一定的復雜性,所以通常需要謹慎的選擇這種技術。如果使用該技術,需要確保各個流程有很好的監控機制,并考慮使用關聯ID,這種機制可以幫助你對跨進程的請求進行跟蹤。 4.9服務及狀態機 不管選用REST還是SOAP的RCP機制,服務即狀態機的概念都很強大,服務應該根據限界上下文進行劃分。我們的客戶微服務應該擁有與這個上下文中行為相關的所有邏輯。 當消費者想要對客戶做修改時,它會向客戶服務發送一個合適的請求。客戶服務根據自己的邏輯決定是否接受該請求。客戶服務控制了所有與客戶生命周期相關的事件。 我們要避免簡單的對CRUD進行封裝的貧血服務,如果出現了在客戶服務之外與其進行相關的修改的情況,那么你就失去了內聚性。 把關鍵領域的生命周期顯示建模出來非常有用。我們不但可以再位移的一個地方處理狀態沖突,而且可以在這些狀態變化的基礎上封裝一些行為。 可以認為基于HTTP的REST相比其他集成技術更合理,但是不管你選擇什么技術,都要記住上面的原則。 4.10響應式擴展 響應式擴展提供了一種機制,在此之上,你可以把多個調用的結果組裝起來并在此基礎上執行操作。調用本身可以是阻塞或者非阻塞的。響應式擴展改變了傳統的流程,以往我們會獲取一些數據,然后基于此進行操作,現在可以做的是簡單的對操作的結果進行觀察,結果會根據相關數據的改變自動更新。 很多響應式擴展實現都在分布式系統中,因為調用的細節被屏蔽了,所以事情也更容易處理,我們可以簡單地對下游服務調用的結果進行觀察,而不需要關心它是阻塞還是非阻塞的,唯一需要做的就是等待結果并作出響應。其優點在于,我可以把多個不同的調用組合起來,這樣就可以更容易的對下游服務的并發調用做處理。 4.11微服務世界中的DRY和代碼重用的危險 微服務系統中應該避免系統行為和知識的重復。如果有相同的代碼干同樣的事情,那么代碼就會變大的越來越大,從而降低可維護性。 使用DRY可以得到重復性比較好的代碼。如果隨意創建可用的代碼庫,在微服務中進行服用,對微服務系統來說可能比較危險。 我們需要避免消費者和提供者之前的代碼耦合,否則對微服務任何小的改動都會引起消費者的改動。而 代碼庫就有可能引入這種耦合。 跨服務公用代碼庫很有可能會引入耦合。但使用類似日志庫這樣的公共代碼就沒有什么問題。 經驗是:在微服務內部不用違反DRY,但在跨服務的情況下可以適當范圍DRY。服務之間引入大量的耦合會比重復代碼帶來更糟糕的問題。 4.12按引用訪問 如何傳遞領域實體的相關信息是一個值得討論的問題?可以遵守一個原則:即A服務應該是關于A服務的唯一可靠來源。 使用資源的URI,例如客戶端訂單的通知郵件,在郵件中可以發送關于訂單的URI,而不是具體的訂單信息,防止在發送郵件后,訂單發生了改變而導致訂單不是最新的數據。 當然在使用引用時也需要做一些取舍。如果總是從客戶服務 去查詢給定客戶的相關信息,那么客戶服務的負載會變大,如果在獲得資助的同事,可以得到資源的有效性時限信息的話,就可以進行相應的換粗,從而減少服務的負載,HTTP在緩存控制方面提供了很多現場的支持。 4.13版本管理 每次提及微服務的是偶,需要考慮如果進行版本管理。 4.13.1盡可能推遲 減少破壞性修改影響的最好辦法就是盡量不要做這樣的修改。比如數據庫集成很容易引入破壞性的修改。使用REST就是很好的辦法,因為對于內部的實現的修改不太容易引起服務接口的變化。 另一種延遲破壞性修改的關鍵是鼓勵客戶端的正確行為,避免過早的將客戶端和服務端緊密綁定起來。 客戶端盡可能靈活的消費服務響應應該符合Postel法則,該法則認為,系統中每個模塊都應該“寬進嚴出”,即對自己發送的東西要嚴格,對接收的東西則要寬容。在請求/響應的場景下,該原則可以幫助我們在服務發送改變時,減少消費方的修改。 4.13.2及早發現破壞性的修改 及早發現對消費者產生【破壞的修改非常重要,因為即使使用最好的技術,也很難避免破壞性修改的出現。 最好的方式是使用消費者驅動的契約來及早定位這些問題。 4.13.3使用語義化的版本管理 如果一個客戶端能夠僅僅通過查詢服務的版本號,就知道他是否能夠與之進行對接,那就是最好的。“語義化版本管理”就是一種能夠支持這樣方式的規則說明。 "語義話版本管理"的每個版本號都遵守這樣的格式:major.minor.patch。其中major的改變意味著其中包含向后不兼容的修改;minor的改變意味著有新功能的增加,但應該是向后兼容的;patch的改變代表對已有功能的缺陷修復。 4.13.4不同的接口共存 如果已經做了所有可以做的事情來避免對接口的修改,那么下一步的任務就是限制其影響。我們不想強迫客戶端跟隨服務端一起升級,因為系統微服務可以獨立于彼此進行發布。我們使用過的一種成功的方式就是:在同一個服務商使用新接口和老接口同時存在。所以在發布一個破壞性修改是,可以部署一個同時包含新老接口的版本。 4.13.5同時使用多個版本的服務 另一種經常被提起的版本管理方法時同時運行不同版本的服務,然后把老用于路由到老版本的服務,而新服務可以看到新版本的服務。 缺點是如果要修復一個內部bug,需要修復兩個版本,并做兩次部署。而且可能需要在代碼庫中拉取分支,這個也會引起很多問題。其次吧用戶路由到正確的服務中也是一件比較復雜的事情。最后不同版本的服務的持久化問題,不同版本的服務如果都存儲到同一個數據庫中,并且他們對不同的服務均課件,這可能引入更多的復雜性。 短期內同時使用兩個不同版本的服務時合理的,尤其是當你做藍綠部署或者金絲雀發布時。在這些情況下,不同版本的服務可能共同存在幾分鐘或者幾小時,而且一般只會有兩個版本。 4.14用戶界面 4.15與第三方軟件集成 第一個你的組織對軟件的需求幾乎不可能完全有內部滿足。 第二個事都自己組織開發的話效率非常低。 如果考慮一個軟件是否自己開發:如果某個軟件非常棒,并且它是你的戰略性資產的話,那就自己構建,如果不是這么特別的話,那就購買。 4.15.1缺乏控制 類似使用CMS或者SAAS這樣的產品時,如果與之進行集成對其進行擴展,因為大部分的技術都不受你的控制。這些都需要依賴于廠家所做的決定。 4.15.2定制化 很多企業的工具都聲稱可以做深度的定制化。一定要小心。這些定制化會非常昂貴。 4.15.3意大利面式的集成 另一個挑戰是如何與工具進行集成,服務間的集成是一件非常重要的事情。 4.15.4在自己可控的平臺進行定制化 可以使用定制化軟件所提供的API進行集成。 4.15.5絞殺者模式 絞殺者模式可以捕獲并攔截對老系統的調用。
轉載于:https://www.cnblogs.com/use-D/p/9912456.html
總結
- 上一篇: 微信小游戏视频激励广告onClose接口
- 下一篇: App启动流程