這個話題源自於我司一個項目的技術架構升級,由原來的模塊編程調整為微服務編程,鵬哥所在的研發部負責提供升級的解技術支持。之前系統由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服務的節點上,如下圖:

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

服務發現治理
在微服務模式下,我們來想一下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,並綁定在一起。
最基本的定義方法。

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

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

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

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


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

分別啟動 client 和Server

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

client端日誌:

server 端日誌:

至此我們使用 rabbit mq 模擬了一個同步的調用,下邊我們來模擬我們的高可用和可伸縮。
修改服務端的啟動配置,以便我們可以啟動多個實例。
在啟動完一個實例之後,修改端口號,再次啟動,你會發現已經起了兩個實例。
我們來啟動三個服務端實例,來模擬負載均衡。

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

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



至此我們演示完了這個簡易分佈式系統的所有的功能。
項目代碼地址:https://github.com/linghuxiong/spring-boot-demo/tree/master/spring-boot-rabbit-rpc
閱讀更多 程序猿鵬哥 的文章