摘要:本文屬於原創,歡迎轉載,轉載請保留出處:https://github.com/jasonGeng88/bloggit
原文:https://www.confluent.io/blog/build-services-backbone-events/github
對許多人來講,微服務是創建在請求和響應協議之上的,如 REST 等等。這種方法很天然。 咱們編寫程序是一回事,咱們調用其餘代碼模塊,等待響應並繼續。它也與咱們天天看到的大量使用狀況緊密相連:前面的用戶點擊按鈕的網站,並期待事情發生。數據庫
可是當咱們進入許多獨立服務的世界時,事情就會開始變化。隨着服務的數量隨着時間的推移逐漸增加,同步交互的網絡也隨之增加。之前良性的可用性問題開始引起更普遍的中斷。編程
在分佈式系統中,排查問題對於咱們不幸的運維工程師來講,將是艱鉅的任務。瘋狂的從一個服務到另外一個服務,拼湊各個服務的信息片斷。瀏覽器
這是一個衆所周知的問題,而且有一些解決方案。一個是確保您的我的服務具備比您的系統更高的 SLAs。Google 提供了這樣作的協議。另外一種方法是簡單地分解將服務綁定在一塊兒的同步關係。緩存
咱們可使用異步做爲這樣作的機制。若是你在在線零售工做,你會發現同步接口,如 getImage() 或 processOrder() 感受天然,指望調用獲得當即響應。可是當用戶點擊「購買」時,它會觸發一個複雜而異步的過程。一個採購的過程,並將其線下交付到用戶門口,這種方式已經超出本來按鈕的上下文。所以,將軟件分解成異步流可使咱們可以區分咱們須要解決的不一樣問題,並容許咱們擁抱一個自己就是異步的世界。服務器
在實踐中,咱們發現會有輪詢數據庫表來進行更改,或者經過一些定時任務來進行更新。這些是打破同步關係的簡單方式,但這些方式給人不透明的感受,像有黑客篡改了你的數據同樣。可能他們有一個很好的理由。網絡
因此咱們能夠將全部這些問題集中到一個觀察中。咱們命令服務去作咱們要求的事情,這樣的命令式編程模式不適合獨立運行的服務。架構
在這篇文章中,咱們將看看架構硬幣的另外一面:不是經過命令鏈,而是經過事件流的方式來組合服務。這是一個有效的方法。它也爲咱們將在本系列後面討論的更先進的模式造成基準,咱們將事件驅動處理的想法與流式平臺中的觀點相結合。運維
在咱們深刻研究一個例子以前,咱們須要解決三個簡單的概念。服務能夠經過三種方式相互交互:命令、事件和查詢。 若是你之前沒有考慮過這三者之間的區別,那麼這很值得。
事件的優勢是事實和觸發。外部數據能夠被系統中的任何服務來重用。但從服務的角度來看,事件形成的耦合又要比命令和查詢來得低。這個事實很重要。
服務間交互的三種方式:
命令:是一個動做,在另外一個服務中執行某些操做的請求。有些會改變系統狀態。命令會期待一個響應結果。
事件:既是事實也是觸發器。對已經發生事情的一種通知。
查詢:是查找某物的一個請求。重要的是,查詢是無反作用的,不會形成系統狀態的改變。
咱們從一個簡單的例子開始:客戶訂購小部件,這個行爲會觸發接下來的兩個事情:
處理相關的付款。
系統檢查以查看是否須要更多小部件。
在請求驅動的方法中,這能夠表示爲一連串的命令。目前沒有查詢。互動將以下所示:
要注意的第一個事情是經過調用訂單服務初始化「購買更多的庫存」的業務流程。這混合了兩個服務的責任,理想狀況,咱們應該更好的分離關注點。
如今,若是咱們可使用一個事件驅動方法來表明相同的流程,那麼事情會變得更好。
UI 服務觸發一個「訂單請求」事件,並在返回給用戶以前等待一個「訂單肯定」(或拒絕)的事件。
訂單服務與庫存服務都經過觸發的事情來進行響應。
仔細看,UI 服務與訂單服務的交互沒有任何改變,除了他們是經過事件來交流的,而不是直接調用對方。
庫存服務頗有趣。訂單服務再也不告訴它要作什麼,也再也不控制是否參與互動。這是這種類型的架構很是重要的屬性,稱爲 「Receiver Driven Flow Control」。邏輯被推送到事件的接收者,而不是發送者。責任的重任進行了翻轉!
將控制轉移到接收者可減小服務之間的耦合,從而爲架構提供了重要的可插拔性。組件能夠輕鬆地換入換出。
隨着架構變得愈來愈複雜,可插拔性的這一要素變得愈來愈重要。說咱們要添加一個實時管理訂價的服務,根據供需調整產品的價格。在一個命令驅動的世界裏,咱們須要引入一個能夠由庫存服務和訂單服務調用的 maybeUpdatePrice() 方法。但在事件驅動的世界從新訂價只是一種訂閱共享流的一個服務,當知足相關標準時發送價格更新。
上面的例子只考慮了命令/事件。 沒有查詢操做(請記住咱們將全部交互定義爲命令、事件和查詢的其中一種)。查詢是除了最簡單的架構以外的全部架構的必需品。因此咱們來擴展這個例子,讓訂單服務檢查在處理付款以前有足夠的庫存。
對此的請求驅動方法將涉及向庫存服務發送查詢以檢索當前庫存數量。這致使混合模型,其中事件流純粹用於通知,容許任何服務進入流程,但查詢直接轉到源。
對於服務須要獨立發展的更大的生態系統,遠程查詢增長了不少耦合,在運行時將服務捆綁在一塊兒。 咱們能夠經過內部化來避免這種跨上下文的查詢。事件流用於緩存每一個服務中的數據集,使其能夠在本地進行查詢。
因此要添加這個庫存檢查,訂單服務將訂閱庫存事件流,將它們進行本地存儲。而後直接查詢本地緩存來驗證是否有足夠的庫存。
純事件驅動系統沒有遠程查詢的概念 - 事件將狀態傳播到在本地進行查詢的服務
這種「按事件傳播查詢」方法有三個優勢:
更好的解耦:查詢是本地的。它們不涉及跨上下文調用。這種服務的耦合性遠遠低於他們請求驅動時的耦合性。
更好的自治:訂單服務具備庫存數據集的私有副本,所以它能夠隨意使用它,而不只限於庫存服務提供的查詢功能。
高效鏈接:若是咱們在每一個訂單上「查詢庫存」,咱們將有效地經過兩個服務之間的網絡進行鏈接。隨着負載的增長,或者更多的資源被使用時,這可能會變得很是糟糕。按事件傳播查詢經過將查詢(和鏈接)本地化來解決此問題。
這種作法不是沒有缺點。服務內部變成有狀態的。他們須要跟蹤和處理一段時間內傳播的數據集。狀態的重複也可能使一些問題更難理解(咱們如何原子地減小庫存數量?),咱們也應該注意數據是最終一致性的問題。可是,全部這些問題都有可行的解決方案,他們只須要考慮一下。
適用於這種風格的系統的有用原則是將特定類型的傳播事件的責任分配給單個服務:單獨寫原則。所以,庫存服務部門將擁有「庫存清單」如何隨時間推動,訂單服務部門將擁有訂單等。
這有助於經過單個代碼路徑(儘管不必定是單個進程)來保證一致性,驗證和其餘「寫入路徑」問題。因此,在下面的示例中,請注意,訂單服務控制對訂單進行的每一個狀態更改,但整個事件流量跨越訂單、付款和發貨,每一個由其各自的服務管理。
分配事件傳播的責任很重要,由於這些不只僅是短暫的事件,也不是短暫的聊天。他們表明共同的事實,數據在外面。所以,隨着時間的推移,服務須要負責策劃這些共享數據集:修復錯誤,處理模式變化等狀況。
這裏每一個顏色表明 Kafka 在訂單、發貨和付款中的一個主題(topic)。當用戶點擊「購買」時,會觸發「訂單請求」,等待「訂單確認」事件,而後再回復給用戶。另外三個服務處理與其工做流程部分相關的狀態轉換。例如,付款處理完成後,訂單服務將訂單從驗證推送到已確認。
對於上面描述的一些模式看起來像企業消息(Enterprise Messaging),但仍是有些許的不一樣。企業消息在實踐中側重於狀態的轉移,經過網絡有效地將數據庫捆綁在一塊兒。
事件協做是關於服務經過一系列事件來處理一些業務目標,這些事件將觸發服務的行動。因此這是業務處理的一種模式,而不是簡單的移動狀態的機制。
可是,咱們一般但願在咱們構建的系統中利用這種模式的「faces」。事實上,這種模式的美妙之處在於它能夠處理微觀和宏觀,或者在有意義的狀況下被混合。
組合模式也很常見。 咱們可能想要遠程查詢的靈活性,而不是本地維護數據集的開銷,特別是在數據集增加時。這使部署簡單的功能變得容易(若是咱們要組合輕量級、無服務器架構和事件流,這一點就很重要),或者由於咱們處於無狀態的容器或瀏覽器。
訣竅是限制這些查詢接口的範圍,理想狀況下是在界限上下文中。一般狀況下,具備多個特定目標視圖的架構會比單一的共享數據存儲的架構要好。(界限上下文,這裏是一組共享相同的部署週期或領域模型的服務。)
爲了限制遠程查詢的範圍,咱們可使用集羣上下文模式。這裏事件流是上下文之間的惟一溝通模式。可是,上下文中的服務會利用他們所需的事件驅動處理和請求驅動視圖。
在下面的例子中,咱們有三個部分,只經過事件相互溝通。 在每個內部,咱們使用更細粒度的事件驅動流。 其中一些包括視圖層(查詢層)。這平衡了耦合的便利性,容許咱們將細粒度的服務與較大的實體相結合; 傳統的應用程序或現成的產品中都存在許多真實的服務場景。
集羣上下文模式
事件驅動服務的五大優點:
解耦:若是基於事件,服務能夠更容易地插入到現有的事件流中,或者重作工做流的一些子集。
離線/異步流:服務卸下了保證交付給消息協商器(broker)的責任。這使得以事件驅動的方式輕鬆管理離線任務。
狀態轉移:事件流提供了一種分發數據集的有效機制,所以能夠在界限上下文中重構和查詢。
鏈接:從不一樣的服務組合/加入/擴充數據集更容易。鏈接是快速和本地化的。
可追溯性:當有一箇中心的、不可變的,保持敘事性的,每次互動隨着時間的推移而平常化的記錄時,調試分佈式將變得更加容易。
因此在事件驅動的方法咱們使用事件代替命令,事件觸發處理。它們也變成咱們能夠在本地查詢的視圖。咱們在必要時回到遠程同步查詢,特別是在較小的生態系統中,可是在較大的系統中咱們限制了它的範圍(理想狀況下,僅限於單個界限上下文)。
但全部這些方法只是模式。一塊兒構建系統的指導原則。對於它們,咱們不該該太教條化。例如,若是它是不多改變的東西(好比單點登陸服務),一個全局查詢服務仍然是一個好主意。
訣竅是從事件的基準開始。事件提供了更少的服務機會來將本身相互聯繫起來,而且將流程控制轉移到接收者使得更好的分離關注和更好的可插拔性。
關於事件驅動方法的另外一個有趣的事情是,它們對於大型、複雜的架構,和對於小型、高度協做的架構一樣起到很好的做用。事件的支柱爲服務提供自由發展所需的自治權。去除了複雜的命令和查詢關係。對於運維工程師來講,系統排查仍然是痛苦的,可是但願不是那麼頻繁,至少如今這個故事還有一個腳本!
可是,隨着全部這些事件的討論,咱們談到了不多的分佈式日誌或流處理。當咱們將這種模式與 Kafka 應用時,系統的規則就會改變。協商器的保留特性成爲咱們能夠設計的工具,容許咱們接觸外部的數據。某些服務能夠參考。
流式平臺很是天然地與這種模型的處理事件和構建視圖相配。直接嵌入到服務中的視圖,遠程服務查詢的視圖,或視圖實現做爲一個持續的流。
這致使了一系列優化:利用事件流和事件存儲之間的對偶性,混合流式處理工具,篩選敘述,加入來自許多服務的流和實現咱們能夠查詢的視圖。這些模式正在賦予權力。它們容許咱們使用專門用於處理事件流的工具集來從新構想業務處理。可是,全部這些優化都是基於這裏討論的概念,僅僅應用於更現代的工具集。
在下一篇文章中,咱們將經過考慮將日誌的保留屬性做爲咱們的服務生態系統的組成部分來使數據更具肯定性。咱們將數據保存在外部做爲能夠依賴的中央共享敘事的地方。