SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

這個話題源自於我司一個項目的技術架構升級,由原來的模塊編程調整為微服務編程,鵬哥所在的研發部負責提供升級的解技術支持。之前系統由4個模塊組成,約定4個模塊是通過Activity MQ解耦,但是模塊之間難免會有一些需要等待對方返回才能下一步的調用,之前的解決方案是使用Activity MQ 的同步調用,現在系統升級改為使用RabbitMQ代替原來的ActivityMQ,異步的替換沒有啥問題,但是同步的時候負責的開發就有點懵了,因為我的同事並沒有提供rabbit mq 同步調用的方案,項目升級因此中斷而造成了延誤。

中午正好跟項目的負責人一起吃飯,他一直在跟我吐槽我們研發部對他們項目升級支持不夠,沒有提供MQ同步方法的支持,鵬哥聽了火冒三丈,腦子一熱直接懟了過去:MQ 產生的意義是什麼,MQ的出現就是為了解耦,你們把他用在同步調用上,還怎麼實現解耦,同步調用直接rest api 調用不就行了,使用MQ多此一舉。對面項目經理瞬間無話,至此切換下一個話題。

吃完飯回來,冷靜下來之後,想想MQ支持同步其實也不是沒有好處,比如:

  • 多實例間負載均衡
  • 服務發現治理
  • 快速服務橫向擴容

幸虧當時對方經理對rabbitMQ不是很熟,不然丟人估計就丟大了,哈哈。

好了廢話不多說,我們言歸正卷。rabbit 實現同步調用其實依賴與其私有隊列和發送確認,既利用聲明一個不指定名稱的隊列,靠rabbit 來生成一個唯一的隊列名,同時將這個隊列名賦值給消息頭裡面的reply_to字段,以告訴生產者,這是一個返回信息。

原理很簡單,我們來看一個具體的案例。我們先假設服務A需要調用服務B來完成A的業務邏輯,加入服務B有兩個節點。

多實例間負載均衡

我們先來想一下,模塊編程時代,我們是如何實現負載均衡的:我們會在服務B外層架一個負載均衡服務器,如nginx 等,然後再A調用B的時候,其實是通過nginx 代理的方式,路由到其中一個B服務的節點上,如下圖:


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

這種行業的標準解決方案已經被業界無數次的驗證可行性,這裡就不多贅述了。現在我們來看一下MQ是怎麼實現負載均衡的。這其實MQ天然的特性,並不是因為使用了同步調用才有的。在多個消費者監聽同一個隊列的時候,rabbit mq 使用簡單的輪訓以實現消費者的均衡,這種方式巧妙的將負載粗暴的平均到每一個消費實例上。rabbit mq在同步調用案例中,負載均衡的示意圖如下:


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

服務發現治理

在微服務模式下,我們來想一下eureka的作用。服務A需要調用服務B的一個接口,但是它並不知道這個服務B在那臺服務上,也不知道服務B有多少臺可以工作的實例,這個時候eureka就出場了。首先服務A和服務B都註冊到eureka服務器上,並把eureka的服務列表copy一份到服務器的本地,這樣當A需要調用服務B的接口的時候,就可以根據服務路由錶快速的找到可以提供服務的實例。
那模塊編程時代,這個功能靠什麼實現呢?rabbit mq 有一次拯救了我們。服務A在調用B的接口的時候,並不需要知道提供服務的是誰,只需要往對應的queue上發送消息即可,rabbit mq 負責將消息推送給監聽這個queue的消費者。

快速服務橫向擴容

擴容這個問題,如果是使用Nginx 作為代理服務器的話,每次擴容都需要修改Nginx的配置,將新的服務器信息添加到Nginx配置信息中去。這種方式給每一次的擴容帶來工作量和風險。rabbit mq 在對待這個問題上也是天然的支持,當我們發現服務B 當前實例不能滿足現在的請求壓力的時候,我們只需要將B新部署實例連上對應的隊列即可,無需修改任何配置。當我們不需要的這麼多實例的時候,也可以直接停掉即可,也不用修改任何配置。

一個基於Spring boot的例子

說了這麼理論,現在我們來實現我們吹過的牛。使用Spring boot + rabbit 來實現一個分佈式系統的雛形。

新建一個client的項目,配置DirectExchange 和一個Queue,並綁定在一起。

SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

最基本的定義方法。

SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

為了方便測試,我們提供一個rest api 負責發送和接受同步返回的消息。發送MQ的時候我用使用convertSendAndReceive 代替convertAndSend 來實現同步的調用,並接收返回值。

SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

好了。client 端就完成了,現在我們來編寫服務端。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

編寫一個receiver,負責監聽client端定義的queue。並將Process 方法添加返回值。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

你也許會好奇,為什麼我沒有配置rabbit的任何信息,這是因為,你要你在POM中引入了 rabbit的jar,並且沒有修改默認的rabbit的用戶名和密碼,Spring boot 會默認幫你鏈接到rabbit上的。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

使用docker 啟動一個 MQ的實例,如果不會Docker 啟動MQ 鵬哥會提供另外一篇博客來簡單介紹一下。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

分別啟動 client 和Server


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

使用Postman 請求一下client 的API來模擬一個請求,同時client 會去向 Server 發送一個同步請求。

SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

client端日誌:

SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

server 端日誌:


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

至此我們使用 rabbit mq 模擬了一個同步的調用,下邊我們來模擬我們的高可用和可伸縮。

修改服務端的啟動配置,以便我們可以啟動多個實例。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

在啟動完一個實例之後,修改端口號,再次啟動,你會發現已經起了兩個實例。

我們來啟動三個服務端實例,來模擬負載均衡。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

使用postman 請求6次剛才的接口,我們在client 端 的日誌裡面看到了6個請求的返回值。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

同時均勻的在服務端的3個實例上,分別承載了兩次調用。


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統


SpringBoot + RabbitMQ 實現高可用可伸縮的簡易版分佈式系統

至此我們演示完了這個簡易分佈式系統的所有的功能。

項目代碼地址:https://github.com/linghuxiong/spring-boot-demo/tree/master/spring-boot-rabbit-rpc


分享到:


相關文章: