福彩时时彩5d开奖结果:是時候改變你對微服務的認知了!

2018-02-28 16:49:05 ortotra 559

陕陕西福彩快乐十分开奖结果 www.zocum.com 大部分時候,微服務都是建立在一種基于請求和響應的協議之上。比如,REST等。這種方式是自然的。我們只需要調用另外一個??榫褪橇?,然后等待響應返回,然后繼續。這樣的方式確實也滿足了我們的很多的場景:用戶通過點擊頁面的一個按鈕然后希望發生一些事情。


但是,當我們開始接觸許多獨立的service的時候,事情就發生改變了。隨著service數量急速的增長,同步交互比例也隨著service在急速增長。這時候,我們的service就會遇到很多的瓶頸。

于是,不幸的ops工程師們就被我們坑了,他們疲憊的奔波于一個又一個的service,拼湊在一起的二手信息片段,誰說了什么,去往哪里,什么時候發生?等等。。。

這是一個非常典型的問題。市面上也有一些解決方案。一種方案就是確保您的個人服務具有比您的系統更高的SLA。 Google提供了這樣做的協議。另一種方法是簡單地分解將服務綁定在一起的同步關系。


上面的做法都沒有從模式上根本解決問題。我們可以使用異步機制來解決這個問題。比如,電商網站中你會發現這樣的同步接口,比如getImage()或者processOrder(),也許你感覺蠻正常。調用了然后希望馬上有一個響應。但當用戶點擊了“購買”后,觸發了一個復雜且異步的處理過程。這個過程涉及到購買、送貨上門給用戶,這一切都是發生在當初的那一次的按鈕點擊。所以把一個程序處理邏輯切分成多個異步的處理,是我們需要解決的問題。這也正符合我們的真實的世界,真實世界本來就是異步的,擁抱異步吧。


在實際情況下,我們其實已經自動擁抱了異步了。我們發現自己會定時輪詢數據庫表來更改又或者通過cron定時job來實現一些更新。這些方法都是一些打破同步的方式,但是這種做法總讓人感覺有種黑客范兒,感覺像是黑客行為,怪怪的。


在本文中,我們將會討論一種完全不同的架構:不是把service們通過命令鏈揉到一塊,而是通過事件流(stream of events)來做。這是一個不錯的方式。這種方式也是我們之后要討論的一系列的一個基礎。


當我們進入正式的例子之前,我們需要先普及三個簡單的概念。一個service與另外一個service有三種交互方式:命令(Commands)、事件(Events)以及查詢(Queries)。


事件的美妙之處在于“外部數據”可以被系統中的任何service所重用。


而且從service的角度來說,事件要比命令和查詢都要解耦。這個很重要。


服務之間的交互有三種機制:


Commands 。命令是一個操作。希望在另一個服務中執行某些操作的一個請求。 會改變系統狀態的東西。 命令期待有響應。

Events 。事件既是一個事實也是一個觸發器。 發生了一些事情,表示為通知。

Queries 。查詢是一個請求,是一個查找一些東西的請求(request)。重要的是,查詢不會使得系統狀態發生改變。


一個簡單事件驅動流程


讓我們開始一個簡單的例子:用戶購買一個小東西。那么接下來要發生兩件事情:


支付。

系統檢查是否還有更多的商品需要被訂購。

在請求驅動(request-approach)的架構中,這兩個行為被表現為一個命令鏈條。交互就像下面這樣:


首先要注意的問題是“購買更多”的這個業務流程是隨著訂單服務(Order Service)一塊被初始化的。這就使得責任不獨立,責任跨了兩個service。理想情況下,我們希望separation of concerns,也就是關注隔離。


現在如果我們使用事件驅動,而不是請求驅動的方式的話,那么事情就會變得好一些。


在返回給用戶之前,UI service 發布一個OrderRequested事件,然后等待OrderConfirmed(或者Rejected)。

訂單服務(Orders Service)和庫存服務(Stock Service) react這個事件。


仔細看這里,UI service和Orders Service并沒有改變很多,而是通過事件來通信,而不是直接調用另一個。


這個Stock service(庫存服務)很有趣。Order Service告訴他要做什么。然后StockService自己決定是否參與本次交互,這是事件驅動架構非常重要的屬性,也就是:Reciver Driven Flow Control,接收者驅動流程控制。一下子控制反轉了。


這種控制反轉給接收者,很好的解耦了服務之間的交互,這就為架構提供了可插拔性。組件們可以輕松的被插入和替換掉,優雅!


隨著架構變得越來越復雜,這種可插拔性的因素變得更加重要。舉個例子,我們要添加一個實時管理定價的service,根據供需調整產品的價格。在一個命令驅動的世界里,我們就需要引入一個可以由庫存服務(Stock Service)和訂單服務(Orders Service)調用的類似updatePrice()這樣的方法。


但是在事件驅動(event-driven)世界更新價格的話,service只需要訂閱共享的stream就是了,當相應的條件符合時,就去執行更新價格的操作。


事件(Events)和查詢(Queries)的混合


上面的例子只是命令和事件。并沒有說到查詢。別忘了,我們之前可是說到了三個概念。現在我們開始說查詢。我們擴展上面的例子,讓訂單服務(Orders Service)在支付之前檢查是否有足夠的庫存。


在請求驅動(request-driven)的架構中,我們可能會向庫存服務(Stock Service)發送一個查詢請求然后獲取到當前的庫存數量。這就導致了模型混合,事件流純粹被用作通知,允許任何的service加入flow,但查詢卻是通過請求驅動的方式直接訪問源。


對于服務(service)需要獨立發展的較大的生態系統,遠程查詢要涉及到很多關聯,耦合很嚴重,要把很多服務捆綁在一起。我們可以通過“內部化”來避免這種涉及多個上下文交叉的查詢。而事件流可以被用于在每個service中緩存數據集,這樣我們就可以在本地來完成查詢。


所以,增加這個庫存檢查,訂單服務(Order Service)可以訂閱庫存服務(Stock Service)的事件流,庫存一有更新,訂單服務就會收到通知,然后把更新存儲到本地的數據庫。這樣接下來就可以查詢本地這個“視圖(view)”來檢查是否有足夠的庫存。


純事件驅動系統沒有遠程查詢的概念 - 事件將狀態傳播到本地查詢的服務


通過事件來傳播( “Queryby Event Propagation”)的查詢有以下三個好處:


1、更好的解耦:在本地查詢。這樣就不涉及跨上下文調用了。這種做法涉及到的服務們遠遠不及那種”請求驅動”所涉及到的服務數量多。

2、更好的自治:訂單服務(Order Service)擁有一份庫存數據集的copy,所以訂單服務可以任意使用這個本地的數據集,

而不是說像請求驅動里的那樣僅僅只能檢查庫存限額,而且只能通過Stock Service所提供的接口。

3、高效Join:如果我們在每次下訂單的時候都要去查詢庫存,就要求每次都要高效的做join,通過跨網絡對兩個service進行join。隨著需求的增加,或者更多的數據源需要關聯,這可能會變得越來越艱巨。所以通過事件傳播來查詢(Query by Event Propagation)將查詢(和join)本地化后就可以解決這個問題(就是本地查詢)。


但這種做法也不是沒有缺點。 Service從本質上變得有狀態了。這樣就使得他們需要被跟蹤和矯正這些數據集,隨著時間的推移,也就是你得保證數據同步。狀態的重復也可能使一些問題更難理解(比如如何原子地減少庫存數量?),這些問題我們都要小心。但是,所有這些問題都有可行的解決方案,我們只是需要多一點考慮而已。 


單一寫入者原則(Single Writer Principle)


針對這種風格的系統,也就是事件驅動風格的系統,一個非常有用的原則就是針對指定類型的傳播的事件分配責任的時候,應該只分配給一個單一的service:單一的寫入者。什么意思呢?就是Stock Service只應該處理庫存這一件事情,而Order Service也只屬于訂單們,等等。


這樣的話有助于我們通過單個代碼路徑(盡管不一定是單個進程)來排除一致性,驗證和其他“寫入路徑(writepath)”問題。因此,在下面的示例中,請注意,訂單服務(Order Service)控制著對訂單進行的每個狀態的更改,但整個事件流跨越了訂單(Orders),付款(Payments)和發貨(Shipments),每個都由它們各自的服務來管理。


分配“事件傳播”(event propagation)的責任很重要,因為這些不僅僅是短暫的事件,或者是那種無須保存短暫的聊天。他們代表了共同的事實(facts),以及“數據在外部(data-on-the-outside)“。因此,隨著時間的推移,服務(services)需要去負責更新和同步這些共享數據集(shared datasets):比如,修復錯誤,處理schema的變化等情況。


上圖中每個顏色代表Kafka的一個topic,針對下訂單(Order)、發貨和付款。  當用戶點擊“購買”時,會引發“Order Requested”,等待“Order Confirmed”事件,然后再回復給用戶。 另外三個服務處理與其工作流程部分相關的狀態轉換。 例如,付款處理完成后,訂單服務(Order Service)將訂單從“已驗證(Validated)”推送到“已確認(Confirmed)”。


模式(Patterns)和集群服務(Clustering Services)的混合


上面的說到的模型有點像企業消息(Enterprise Messaging),但其實是有一些不同的。企業消息,在實踐中,主要關注狀態的轉換,通過網絡有效地將數據庫捆綁在一起。


而事件協作(Event Collaboration)則更偏重的是協作,既然是協作就不簡單的是狀態轉換,事件協作是關于服務(service)通過一系列事件進行一些業務目標,這些事件將觸發service的執行。所以這是業務處理(business processing)的一種模式,而不是簡單的轉換狀態的機制。


我們通常希望在我們構建的系統中這種模式具有兩面性。事實上,這種模式的美妙之處在于它確實既可以處理微觀又可以處理宏觀,或者在有些情況下可以被混合。


模式組合使用也很常見。我們可能希望提供遠程查詢的方便靈活性,而不是本地維護數據集的成本,特別是數據集增長時。這樣的話就會讓我們的查詢變得更加的簡單,我們只需要輕松部署簡單的函數就可以了。而且我們現在很多都是無狀態的,比如容器或者瀏覽器,在這種情況下也許遠程查詢是一種合適的選擇。


遠程查詢設計的訣竅就是限制這些查詢接口的范圍,理想情況下應該是在有限的上下文中(context)。通常情況下,建立一個具有多個特定,具體視圖的架構,而不是單一的共享數據存儲。注意是多個具體的視圖,而不是單一的共享數據存儲。(一個獨立(bounded)的上下文,或者說是偏向原子,這里說的原子不是側重微服務中常說的那個“原子服務”。獨立上下文,一般是指有那么一組service,它們共享同一個發布流水線或者是同一個領域模型【domain model】)。


為了限制遠程查詢(remote queries)的邊界(scope),我們可以使用一種叫做“集群式上下文模式(clustered context pattern)”。這種情況下,事件就流純粹是用作上下文之間的通信。但在一個上下文里的具體service們則可以既有事件驅動(event-driven)的處理,同時也有請求驅動(request-driven)的視圖(view),具體根據實際情況需要。


在下面的例子中,我們有三個部分,三個之間只通過事件相互溝通。在每一個內部,我們使用了更細粒度的事件驅動流。其中一些包括視圖層(查詢層)。


還是看下圖吧:


集群上下文模型(Clustered Context Model)


事件驅動(event-driven)五個關鍵好處:

 解耦:把一個很長的同步執行鏈的命令給分解,異步化。 分解同步工作流。 Brokers 或topic解耦服務(service),所以更容易插入新的服務(service),具有更強的插拔性。

離線/異步流:當用戶點擊按鈕時,很多事情都會發生。 一些同步,一些異步。 對能力的設計,無論是以前的,還是將來的,都是更自由的。提高了性能,提高了自由度。

狀態同步更新:事件流對分布式數據集提供了一種有效的機制,數據集可以在一個有界的上下文里被重構(“傳播”或“更新”)和查詢。

 Joins:從不同的服務(service)組合/join/擴展數據集更容易。 join更快速,而且還是本地化的。

可追溯性: 當有一個統一化的,中心化的,不可變的,保持性的地方來記錄每個互動時,它會及時展現,debug的時候也更容易定位問題,而不是陷入一場關于“分布式”的謀殺。(這里有點晦澀)

總結


Ok,在事件驅動的方法中我們使用事件(Events)而不是命令(Commands)。事件觸發業務處理過程。事件也可以用到更新本地視圖上。然后我們向你介紹了,在必要時,我們可以再回到遠程同步查詢這種方式,特別是在較小的系統中,而且我們還將遠程同步查詢的范圍擴大到更大的范圍(理想情況下,還是要僅限于單個獨立的上下文,也就是單個領域模型,不能再擴大了,剛剛好才是真的好)。


而且所有這些方法都只是模式(pattern)。模式就會有框得太死的問題。模式覆蓋不到的地方,我們就要具體情況具體對待了。例如,單點登錄服務,全局查詢的service仍然是一個好主意,因為它很少更新。


這里的秘訣就是從事件的基準出發去考慮問題。事件讓服務之間不再耦合,并且將控制(flow-control)權轉移到接收者,這就有了更好的“分離關注(separated concerns)”和更好的可插拔性。


關于事件驅動方法的另一個有趣的事情是,它們對于大型,復雜的架構同樣適用,就像它們對于小型,高度協作的架構一樣。事件讓service們可以自主的決定自己的所有事情,為服務們提供自由發展所需的自主權。


然后我們向你介紹了事件和查詢混合的場景。說到查詢,在純事件驅動方法中,查詢完全基于本地的數據集,而沒有遠程查詢。本地數據集則是通過事件觸發來更新狀態。然而,很多時候,基于請求驅動的查詢方式在很多時候也是比較方便的,因為本地數據集的方式,狀態的同步更新確實是一件更加需要成本的事情。


然后我們說到了單一寫入z者原則。單一寫入者讓我們數據更新有了統一的入口,有助于我們通過單個代碼路徑(盡管不一定是單個進程)來排除一致性,驗證和其他“寫入路徑(writepath)”問題。


然后我們討論了集群上下文模型。每個領域模型組成一個獨立的區域,然后再由多個區域共同組成一個領域模型集群,模型之間又通過Kafka來交互。每個領域模型里又可以包含幾種模式的混合,比如Events、Views、UI,這些里邊可以既有事件驅動模式,又有請求驅動模式。


大體就這么多。


感謝Antony Stubbs,Tim Berglund,Kaufman Ng,GwenShapira和Jay Kreps,他們幫助我們回顧了這篇文章。


譯者曰:最近也恰好在做有關事件流的內容,對本文中講到的異步解耦和拆解同步請求鏈條過長問題深有感觸,也非常認同。另外最近有人聊到有關數據庫查詢效率問題,通過閱讀本文也許會讓你對查詢有一個全新的認識。這些微服務理念看起來好像專屬于“微服務”,好像其他人就不需要了解一樣。其實也許微服務的這些先進理念就像其他任何的先進的架構理念一樣,他們都是我們軟件架構知識體系的儲備之一,也許在哪天你正在進行的項目遇到了瓶頸,沒準本文討論的這些內容就能派上用場了,不僅僅限于本文舉的那個例子。


微服務"交互方式"觀念轉變:


 是時候更新一下你對于構建微服務的一些知識體系了。如果你認為REST就是微服務構建的主要交互方式的話,那么也許你錯了;如果你認為rpc就是構建微服務的的主要交互方式的話,那么也許你又錯了。


因為這兩種都屬于一種類型,那就是他們都屬于請求驅動(request-driven)模式,而這種模式很多時候是同步的,一條鏈上掛了很多的服務調用,勢必在鏈條變長后,性能堪憂。


本文向你推薦了一個構建微服務的新的工具,或者說是向你補充了。那就是事件驅動(event-driven)的模式。它解耦、異步,帶來了更好的擴展性和性能。很多時候,同步會讓事情變得異常糟糕!


如果以后有人和討論起微服務的模式的時候,你可以說REST、rpc(請求驅動)以及事件驅動共同混合使用才會構建出更好的微服務來!


ps:文中部分段落翻譯用詞略顯晦澀,我曾嘗試用大白話來翻譯,但發現會損失原意,故請仔細斟酌消化。