說起RabbitMQ大家第一時間應該想到的就是非同步佇列,關於非同步佇列的話題簡直太多了,各位同學在園子裡一搜便知。我第一次聽非同步佇列這個名詞感覺非常高大上?,想到這項技術必須要學。但是學習的任何一門技術沒經過專案的洗禮,都似乎少了點什麼。嗯。是的。只有在企業級開發中,才能找到自己的遺漏的知識點。所以大家一定要想辦法將自己學習到的東西糅合進專案中。以我現在一貫的作風就是學習、鞏固、總結與分享。雖然介紹RabbitMQ這類部落格已經很多了,但是人家寫的畢竟是人家寫的,我要把自己的學習心得記錄下來,一來鞏固自己的知識,二來可以幫到其它的小夥伴。?
首先說一下佇列.佇列的本質是一種先進先出的線性儲存結構。注意不要和棧弄反了,棧的儲存結構是先進後出
那怎樣理解非同步佇列?
舉個例子:假如在電商系統中,使用者下單成功之後。A服務要拿到下單資訊傳送郵件(350ms)給使用者,B服務要拿到下單資訊傳送簡訊(400ms)給使用者,C服務要拿到下單資訊記錄日誌(300ms),如果三個服務利用RPC遠端呼叫下單服務提供的api,在排隊等候時(可能有分散式鎖)將要耗費1050ms,而且耦合太高了,如果下單服務改由另一個服務承擔那麼ABC服務都得跟著改,接下來就變成下面這樣。
使用者下單成功後直接將資料存進訊息佇列(資料庫也會存一份),ABC三服務直接從訊息佇列中拿下單資訊,不再排隊等候(非同步),時間耗費400ms。效能提升了一倍之多。而且誰將資料放入訊息佇列ABC三服務根本不用管,它們現在只認資料不認人.?
再來說一下RabbitMQ.RabbitMQ是採用Erlang語言實現的訊息中介軟體。RabbitMQ幾乎支援所有的常用語言,並且提供了使用者管理介面,可以方便使用者管理和監控訊息。在使用RabbitMQ之前我們先安裝她。這裡的話我只說一下在Linux下使用小鯨魚安裝
docker pull rabbitmq:management
docker run -d -p 15672:15672 -p 5672:5672 --name rabbitmq rabbitmq:management
不出意外的話容器已經啟動了,如果有問題的話就看日誌.
預設情況下訪問RabbitMQ 使用者名稱和密碼都是 "guest" ,但是這個這個賬戶有限制,預設只能通過本地網路訪問,遠端連線的話受限.
這裡的話要執行以下命令. ps:命令是網上找的?
docker exec -it rabbitmq bash --進入容器
rabbitmq-plugins enable rabbitmq_management --開啟外掛命令
這裡再說一下生產者和消費者。生產者就是往訊息佇列中加入(建立)資料的一方。消費者就是拿到(接收)訊息的一方。
接下來在程式碼中簡單演示一下.
新建兩個控制檯程式。引入包RabbitMQ.Client.
生產者程式碼:
static void Main(string[] args) { //建立連線工廠 var factory = new ConnectionFactory { UserName = "guest",//使用者名稱 Password = "guest",//密碼 HostName = "***.**.***.***",//rabbitmq ip Port = 5672,//埠號 }; //建立連線 var connection = factory.CreateConnection(); //建立通道 var channel = connection.CreateModel(); //建立一個佇列,名稱為:RabbitMQStudy channel.QueueDeclare("RabbitMQStudy", false, false, false, null); Console.WriteLine("已連線到RabbitMQ.^-^,可向佇列投遞訊息!"); Console.WriteLine("輸入close則關閉連線"); string text = string.Empty; bool flag = true; while (flag) { Console.WriteLine("請輸入資料......"); text = Console.ReadLine(); switch (text) { case "close": flag = false; continue; default: flag = true; break; } var bytes = Encoding.UTF8.GetBytes(text); //釋出訊息 channel.BasicPublish("", "RabbitMQStudy", null, bytes); Console.WriteLine("訊息已傳送成功!"); Console.WriteLine(new string('-', 100)); } Console.WriteLine("連線已關閉......"); //關閉資源 channel.Close(); //釋放連線 connection.Close(); }
消費者程式碼:
static void Main(string[] args) { //建立連線工廠 ConnectionFactory factory = new ConnectionFactory { UserName = "guest",//使用者名稱 Password = "guest",//密碼 HostName = "***.**.***.***",//rabbitmq ip Port = 5672,//埠號 }; //建立連線 var connection = factory.CreateConnection(); //建立通道 var channel = connection.CreateModel(); //var consumer = new DefaultBasicConsumer(channel); var consumer = new EventingBasicConsumer(channel); //為消費者送達訊息時產生的事件 consumer.Received += (ch, ea) => { var message = ea.Body.ToArray();//接收到的訊息 Console.WriteLine($"接收到資訊:{Encoding.UTF8.GetString(message)}"); //訊息已被消費 channel.BasicAck(ea.DeliveryTag, false); Console.WriteLine("訊息已成功接收並消費!"); Console.WriteLine(new string('-', 100)); }; //啟動消費者並設定為手動應答 String consumerTag = channel.BasicConsume("RabbitMQStudy", false, consumer); Console.WriteLine("消費者已啟動成功!"); Console.ReadKey(); channel.Dispose(); connection.Close(); }
開啟了三個消費者,這時佇列中的訊息會被平均分攤(訂閱了同一個佇列).RabbitMQ有輪詢的機制,所以不是每個消費者都可以收到所有的訊息井處理。
下面說一下三個重要的知識點:交換器、路由鍵、繫結
交換器:生產者向消息佇列傳送訊息資料時,其實訊息先到交換器再由交換器路由到相應的佇列。這裡有同學就會問了,我們剛剛上面的程式碼並沒有申明交換器啊,其實這裡我們傳送訊息時exchange引數為(" "),相當於預設建立好的一個交換器(型別為direct)。直接用RouteKey指定佇列名即可.
交換器一共有四種型別:1.fanout:此型別的交換器會將傳送到該交換器的的訊息路由到所有與該交換器繫結的佇列中 2.directc:如果交換器型別為direct型別,那麼RoutingKey(路由鍵,相當於訊息投送至與交換器繫結的哪個佇列)和BindingKey(繫結鍵,交換器與佇列相繫結有一個BindingKey)需要完全匹配(相同)。 3.topic:topic是升級版的direct型別的交換器,與direct型別交換器相同的是topic型別的交換器也是將訊息路由到RoutingKey與BindingKey相匹配的佇列中。與direct型別交換器不同的是topic型別的交換器有模糊匹配。 例如:我們傳送訊息時會路由到指定RouteKey佇列中.交換器在與佇列繫結的時候,BindingKey可以是模糊型別的.RouteKey和BindingKey都是以點號“.”分割的字串.BindingKey的字串可以存在“ * ”和“ # ”特殊字元。在以點號“ . ”為分割的字串中,“ . ”用於匹配一個單詞," # "用於匹配多個(或零個)單詞. 4.headers:這個交換器我在這裡不做介紹,我沒用過?,上面介紹的三個是最常用的了。
如下圖所示申明瞭一個topic型別的交換器,當路由鍵為core.never.net的訊息會同時路由到佇列1和佇列2中。當路由鍵為go.never.cn的訊息也會同時路由到佇列1與佇列2中。當路由鍵為www.bilibili.com的訊息會路由到佇列2中。
舉個例子,在生產者程式碼中再加幾句程式碼:
//和上面一樣,程式碼省略...
//建立一個佇列,名稱為:RabbitMQStudy channel.QueueDeclare("RabbitMQStudy", false, false, false, null); //建立一個佇列,名稱為:RabbitMQStudy1 channel.QueueDeclare("RabbitMQStudy1", false, false, false, null); //宣告一個交換器 channel.ExchangeDeclare("exange", "topic", true, false, null); //繫結 channel.QueueBind("RabbitMQStudy", "exange","#.routeKey"); //繫結 channel.QueueBind("RabbitMQStudy1", "exange", "*.com");
//和上面一樣,程式碼省略...
//釋出訊息
channel.BasicPublish("exange", "core.routeKey", null, bytes);
//和上面一樣,程式碼省略...
在建一個控制檯程式(消費者),和第一個消費者的程式碼一樣,只不過兩個消費者監聽設定的佇列名不一樣。一個是RabbitMQStudy,一個是RabbitMQStudy1。
//啟動消費者並設定為手動應答 String consumerTag = channel.BasicConsume("RabbitMQStudy1", false, consumer);
執行... ...
可以看到只有消費者1接收到了訊息,模糊匹配的作用就展現出來了.
路由鍵:生產者將訊息傳送給交換器時,會指定一個路由key,用於設定這個訊息的路由規則,路由key需要與交換器型別和繫結key聯合使用才能有效。
繫結:RabbitMQ中通過繫結將交換器與佇列進行關聯,在交換器與佇列進行繫結時會有一個繫結鍵,這樣結合路由鍵RabbitMQ就會知道將訊息投遞到哪個佇列中。
爭對上述程式碼及文字我們來總結一下 訊息佇列的優缺點:1.解耦,我上面提到了,消費者只需要拿資料,資料的投遞方是誰它一點都不過問。 2.削峰.當我們設計一個秒殺業務的時候,加入成千上萬的請求湧入,伺服器的壓力過大。為了緩解壓力我們可以將請求先放入佇列中,伺服器一定時間內可以承受多少請求就拿多少。 3.非同步.我上面也提到了.直接從訊息佇列中拿資料,無需排隊等候,大大提高了系統效能。 RabbitMQ的使用過程:生產者:1.先建立連線工廠,與RabbitMQ建立連線。 2.建立通道。 3.宣告一個交換器,設定交換器型別、是否持久化等。 4.宣告佇列,設定佇列是否排他、是否持久化等。 5.將交換器與佇列繫結,設定RouteKey。 6.傳送訊息至交換器,並設定交換器名稱、路由鍵、訊息主體等。 7.交換器根據路由鍵和繫結的佇列相匹配,投送訊息至佇列中。 8.如果沒有找到相應佇列,會根據生產者配置的屬性丟棄訊息或者退回給生產者。 9.關閉連線。 消費者:1.先建立連線工廠,與RabbitMQ建立連線。 2.建立通道。 3.等待生產者投遞訊息至佇列,消費者接收訊息。 4.消費者確認接收到訊息 5.RabbitMQ從佇列中刪除已確認的訊息。 6.關閉連線。
今天就這麼多,後續文章會一一介紹RabbitMQ其它特性。
如有不足,請見諒!