使用RabbitMQ的事件驅動微服務

jingcaolyh 8年前發布 | 19K 次閱讀 RabbitMQ 微服務

從傳統的HTTP調用遷移到基于事件驅動的微服務架構改變了我們以往的思維方式,其有助于服務的解耦和伸縮,該文介紹了使用RabbitMQ作為消息基礎設施實現的事件驅動系統以及處理隊列的一些模式。

在微服務之間使用正確的模式進行通信有助于應用程序的伸縮以及解決大多數分布式系統的問題。我們一開始是采用直接的HTTP調用來通信的,但后來決定遷移到事件驅動系統上了。該系統改變了我們對于服務之間交互的思維方式,迫使我們采用可伸縮的模式并且提高了我們的適應能力。

我們從傳統的HTTP通信遷移到基于事件的通信有幾個原因,第一是對于服務的強制解耦,從我們對于HTTP的使用經驗來看,我們的服務將會對于需要的每個服務都發起調用請求,這意味著原來的服務對于與其通信的每個服務都需要一個客戶端庫,該客戶端庫需要確保錯誤不會停止或者阻止正常的功能,并且與每個服務保持一致。

當我們擴展到超過20個服務時,維護客戶端庫就成為了一個長期、艱巨的過程。取代舊功能的新服務需要更新所有依賴項。由于所有的這些變動的組件,使得開發和部署的過程更長并更容易出錯。

使用事件的另一個好處是服務不再需要編排功能了,消除了對于客戶端的直接調用。服務可以自由地進出那些已經存在的地方,而無需更新客戶端庫或者添加新的HTTP調用。我們可以迅速部署那些監聽事件的原型應用程序,而不用擔心它們降低整個系統的可用性。

第三點,這個變化允許我們來實現全局模式,我們增加了速率限制和每個工作節點的超時時間,而不用在我們的每一個不同的客戶端庫(GitHub、AWS、內部服務等)分別實現。我們也能夠很容易地實現一個 熔斷器 模式,方式是通過切斷一個事件的監聽器,直到它健康為止,并且這只針對需要改變的工作節點,而不是所有的服務調用者。

最后,我們并不僅限于為那些長時間運行的工作節點持有一個開放的HTTP連接(可以斷開連接或限制打開的套接字等)。

事件和任務

組成事件驅動系統的有兩種不同的模式:事件和任務。

事件是當有事發生時告知已訂閱的應用程序的那些通知。應用程序訂閱某些事件并通過為此創建任務來響應,事件不會直接修改狀態。

任務是修改狀態的動作,唯一可以為一個給定的應用程序創建一個任務的是應用程序本身。這樣的話,應用程序不能直接修改對方的狀態。

當遇到事件和任務的命名時,嚴格的命名約定有助于我們保持一致性和清晰度。任務的命名以應用程序名稱開始這樣可以確保它們只處理預期的應用程序,接下來是模型,其狀態會被任務修改,最后是一個描述性的“現在時”的動詞。一個任務的例子是 api.user.authorize ,根據約定我們知道這個任務是由 api 服務處理的,該任務在 user 對象上執行一個 authorize 動作。

事件沒有應用程序名稱,因為它們可以被多個應用程序訂閱。它們以模型名字開始,并以描述發生了什么的過去式動詞結尾,一個事件的例子就是 user.authorized 。

將我們的應用程序分解為任務和事件迫使我們改變了思維方式,以前,如果我們想在收到付款后發送一封電子郵件,我們會添加一個SendGrid的調用到我們的支付服務里,非常簡單明了。

但是對于我們嶄新的事件系統,我們的支付服務發出了一個事件 org.payment.processed 。我們的email服務Pheidi,就會收到事件并創建了一個任務: pheidi.email.send ,我們現在需要按照反應而不是命令來考慮,不過如果我們需要額外的并且事件沒有提供的數據(比如信用卡注冊名字),我們仍然使用HTTP調用我們的賬單服務。

在基于事件的解決方案伴隨著優勢的同時,還是有一些缺點的,因為你沒有顯式地調用服務,所以不能確定你發出的事件的響應是什么,這使得調試非常困難,因為系統更為復雜和難以理解了。

實現

我們使用RabbitMQ作為我們的消息系統,負責分發事件到監聽它們的服務。任務也會通過RabbitMQ分發,所以可以跨一個應用的多個實例來做負載均衡。我們選擇RabbitMQ是因為它易于部署,并且里邊有準備就緒的供我們使用的NPM客戶端模塊。

?

我們創建了 Ponos 作為我們的統一的工作節點服務器來與RabbitMQ互動,這里有一些我們用來處理隊列的模式。

指數退避算法

從一開始我們就在每個job上添加了指數退避算法,如果一個job拋出了一個可重復的錯誤,那么將會在過一段時間后重試。每個job都從一個最小的時間延遲開始,并且直到達到一個預定義的最大極限值(或者如果沒有定義極限的話,那么就是無窮大),就增加一倍。

最初,我們希望job可以永遠重試,考慮如果有什么東西被“卡住”的話,我們的警報系統將發出警告,我們中的一員將會成為拯救世界的白馬騎士。這一開始會工作得很好,但隨著我們添加了更多的工作,在隊列中被“卡住”的條目的數量將會由于各種原因增長地很快。

最大重試限制&恢復功能

為了應對日益增長的隊列,我們給每個隊列添加了一個最大重試限制,如果job重試達到了給定的次數,我們會阻止它重試并運行恢復功能。恢復功能會記錄并更新帶了一個錯誤的數據庫。現在,我們的警報系統將觸發恢復功能,使我們能夠優先解決問題,而不是將我們的隊列進行備份。我們發現,采用快速失敗機制并給我們的用戶顯示錯誤相對于讓用戶為這些故障等待很長一段時間而言,是更好的選擇。

預讀取

預讀取是RabbitMQ Channel上的一個很重要的設置選項,沒有這個,你的工作節點將利用隊列里的所有可用的job,舉例來說,如果你的應用程序經歷了一個尖峰負載并入列了10000個job,所有的這10000個job都會被發送到工作節點上并存入內存里,這通常會導致機器崩潰,預讀取限制了你的工作節點可以載入內存的job的數量,這篇來自RabbitMQ的 博客文章 會幫助我們確定實現預讀取的最佳方式。

交換機和隊列

為了實現事件和任務,我們使用以下的RabbitMQ結構。任務通過 sendToQueue API來使用一個隊列,由于任務只使用一個應用程序,我們不用為它們創建一個交換機。事件是一個更復雜的設置。事件的發布者會創建一個fanout交換機,每個訂閱者會創建并綁定一個隊列到那臺交換機,這樣允許任何應用程序都可以接收任何事件,而不會影響其他應用程序。

事務IDs

有一樣可以幫助我們調試和反躬自省事件系統的東西是事務ID(TID)。每一個發送到RabbitMQ的job都會帶上一個TID前綴,如果該job是一個事件或者任務的結果,那么就使用相同的TID,如果job不是從事件或者任務創建而來的,我們就產生一個新的TID。這有助于我們追蹤哪些事件引發了哪些要運行的任務。

我們的事件驅動系統加快了我們的開發速度,使我們對于失敗更具有彈性,并為我們的用戶提高了我們產品的響應能力,我們希望這些技術將會一如既往地有助于你們系統的可伸縮性。

原文鏈接: Event-driven Microservices Using RabbitMQ (翻譯:胡震)

 

來自:http://dockone.io/article/1708

 

 本文由用戶 jingcaolyh 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!