一、RabbitMQ簡介
1、什麼是MQ
消息隊列(Message Queue,簡稱MQ),從字面意思上看,本質是個隊列,FIFO先入先出,只不過隊列中存放的內容是message而已。其主要用途:不同進程Process/線程Thread之間通信。
1)爲什麼會產生消息隊列?有幾個原因:
不同進程(process)之間傳遞消息時,兩個進程之間耦合程度過高,改動一個進程,引發必須修改另一個進程,爲了隔離這兩個進程,在兩進程間抽離出一層(一個模塊),所有兩進程之間傳遞的消息,都必須通過消息隊列來傳遞,單獨修改某一個進程,不會影響另一個;
不同進程(process)之間傳遞消息時,爲了實現標準化,將消息的格式規範化了,並且,某一個進程接受的消息太多,一下子無法處理完,並且也有先後順序,必須對收到的消息進行排隊,因此誕生了事實上的消息隊列;
MQ框架非常之多,比較流行的有RabbitMQ、ActiveMQ、ZeroMQ、kafka,以及阿里開源的RocketMQ等。本文介紹RabbitMQ。 瞭解更多消息中間件推薦文章:https://www.cnblogs.com/huojg-21442/p/7601380.html
2、什麼是RabbitMQ
RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件)。RabbitMQ服務器是用Erlang語言編寫的,而集羣和故障轉移是構建在開放電信平臺框架上的。所有主要的編程語言均有與代理接口通訊的客戶端庫。 -- 百度百科
簡單來說,RabbitMQ就是一個開源的消息代理和隊列服務器,並且是基於AMQP協議的,是AMQP的一個實現,支持多種客戶端。
1)爲什麼使用RabbitMQ?
開源的消息中間件
可以跨平臺,跨語言。數據的生成和消費可以是不同的語言。
提供可靠性消息投遞模式,返回模式,在易用性、擴展性、高可用性等方面表現不俗。
與springAMQP完美的整合 集羣模式豐富,表達式配置,HA模式,鏡像隊列模型。
2)RabbitMQ一些基礎概念
Server:又稱Broker,接受客戶端的連接,實現AMQP實體服務。簡單來說就是消息隊列服務器實體。
Connection:連接,應用程序跟Broker的網絡連接。
Channel:消息通道/網絡信道,幾乎所有的操作都是在channel中進行。數據的流轉都要在channel上進行。channel是進行消息讀寫的通道。在客戶端的每個連接裏,可以建立多個channel,每個channel代表一個會話任務。
Message:消息,服務器與應用程序之間傳送的數據,由Properties和body組成。Properties可以對消息進行修飾,比如消息的優先級,延遲等高級特性。body則就是消息體的內容。
Virtual host:虛擬主機/虛擬地址,用於進行邏輯隔離,最上層的消息路由。一個broker裏可以開設多個vhost,用作不同用戶的權限分離。一個虛擬地址裏面可以有多個交換機 exchange和消息隊列message queue。
Exchange:消息交換機,它指定消息按什麼規則,路由到哪個隊列中。接收消息,根據路由機轉發消息到綁定的隊列。
Binding:綁定,交換機和隊列之間的虛擬鏈接,綁定中可以包含routing key。它的作用就是把exchange和queue按照路由規則綁定起來。
Routing Key:路由關鍵字,可以用它來確定如何路由一個特定消息,exchange根據這個關鍵字進行消息投遞。
Queue:消息隊列載體,保存消息並將它們轉發給消費者,每個消息都會被投入到一個或多個隊列。
Producer:消息生產者,就是投遞消息的程序。
Consumer:消息消費者,就是接受消息的程序。
3)消息隊列的使用過程大概如下:
生產者把消息交給服務器,服務器裏面有虛擬主機,主機裏面有AMQP的核心exchange交換機。生產者需要有服務器的ip和端口號,找到服務器,服務器需要把消息投遞到哪個虛擬主機上。
接下來,虛擬主機把消息交給交換機,交換機接收到消息後會根據路由規則找到指定的消息隊列 ,所以生產者生產消息時需要指定消息的routing key。交換機會把消息交給消息隊列。到此,消息的生產者的任務就做完了。
消費者可以監聽消息隊列,由於交換機和消息隊列會進行綁定,消費者會監聽消息隊列message queue,RabbitMQ會把消息隊列交給消費者。
3、開發語言:Erlang – 面向併發的編程語言。
4、什麼是AMQP協議
AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,
AMQP是具有現代特徵的二進制協議。是一個提供統一消息服務的的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,爲面向消息的中間件設計,是一個規範。AMQP的主要特徵是面向消息、隊列、路由(包括點對點和發佈/訂閱)、可靠性、安全。
消息中間件主要用於組件之間的解耦,消息的發送者無需知道消息使用者的存在,反之亦然。
在 AMQP 模型中,消息的 producer 將 Message 發送給 Exchange,Exchange 負責交換 / 路由,將消息正確地轉發給相應的 Queue。消息的 Consumer 從 Queue 中讀取消息。
二、Java入門使用
QueueingConsumer在Rabbitmq客戶端4.x版本就被標記爲@Deprecated:
參考文章:爲什麼 QueueingConsumer 會被 Deprecated ?
RabbitMQ Java客戶端使用 com.rabbitmq.client 作爲其頂級軟件包。關鍵的類和接口是:
com.rabbitmq.client.Connection接口:
com.rabbitmq.client.Channel接口挺重要,包含了很多消息讀寫的重載方法,具體看API
對於類和接口中的方法參考官方API:RabbitMQ Java Client 5.7.3 API
1、使用默認的交換機
創建一個 maven項目,引入依賴 :
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1)消息生產者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4.發佈消息
String routingKey = "test1";
String msg="hello rabbitmq consumer message";
for (int i = 0; i < 2; i++) {
/**
參數:
exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
routingKey -路由鍵
mandatory -如果要設置“強制性”標誌,則爲true
props -消息的其他屬性-路由標頭等
body -消息正文
*/
channel.basicPublish("", routingKey, false, null, msg.getBytes());
}
//5.釋放資源
channel.close();
connection.close();
}
}
2)消息消費者
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4. 創建消息隊列
String queueName = "test1";
channel.queueDeclare(queueName, true, false, false, null);
//5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
/**
參數:
queue -隊列名稱
autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
callback -消費者對象的接口
*/
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
/**
參數:
consumerTag-與消費者相關聯的消費者標籤
envelope -消息的打包數據
properties -消息的內容頭數據
body -消息正文(客戶端特定的不透明字節數組)
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("------------consumer message-----------");
System.out.println("sonsumerTag:" + consumerTag);
System.out.println("envelope:" + envelope);
System.out.println("properties:" + properties);
System.out.println("msg:" + new String(body));
}
});
}
}
三、exchange交換機機制
1、什麼是交換機
rabbitmq的message model實際上消息不直接發送到queue中,中間有一個exchange是做消息分發,producer甚至不知道消息發送到那個隊列中去。因此,當exchange收到message時,必須準確知道該如何分發。是append到一定規則的queue,還是append到多個queue中,還是被丟棄?這些規則都是通過exchagne的4種type去定義的。
exchange是一個消息的agent,每一個虛擬的host中都有定義。它的職責是把message路由到不同的queue中。
2、交換器分類
RabbitMQ的Exchange(交換器)分爲四類:direct(默認)、headers、fanout、topic
其中headers交換器允許你匹配AMQP消息的header而非路由鍵,除此之外headers交換器和direct交換器完全一致,但性能卻很差,幾乎用不到,忽略。
3、Direct Exchange交換機
直連型交換機,也非常的簡單,所有發送到Direct Exchange交換機的消息被轉發到 RouteKey中指定的Queue,消息攜帶的路由鍵與隊列名要完全匹配。
1)消息生產者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4.發佈消息
String exchangeName="test_direct_exchange";
String routingKey = "test.direct";
String msg="hello rabbitmq consumer message - test_direct";
for (int i = 0; i < 2; i++) {
/**
參數:
exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
routingKey -路由鍵
mandatory -如果要設置“強制性”標誌,則爲true
props -消息的其他屬性-路由標頭等
body -消息正文
*/
channel.basicPublish(exchangeName, routingKey, false, null, msg.getBytes());
}
//5.釋放資源
channel.close();
connection.close();
}
}
2)消息消費者
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4. 創建消息隊列和direct交換機,並通過channel讓交換機跟消息隊列進行綁定
String queueName = "test_queue";
String exchangeName="test_direct_exchange";
String exchangeType="direct";
String routingKey="test.direct";
/**
參數:
queue -隊列名稱
durable -如果我們聲明一個持久隊列,則爲true(該隊列將在服務器重啓後保留下來)
exclusive -如果我們聲明一個排他隊列,則爲true(僅限此連接)
autoDelete -如果我們聲明一個自動刪除隊列,則爲true(服務器將在不再使用它時將其刪除)
arguments -隊列的其他屬性(構造參數)
*/
channel.queueDeclare(queueName, true, false, false, null);
/**
參數:
exchange -交易所名稱
type -交易所類型
durable -如果我們聲明持久交換,則爲true(該交換將在服務器重啓後保留下來)
autoDelete -如果服務器在不再使用交換機時應刪除該交換機,則爲true
arguments -用於交換的其他屬性(構造參數)
*/
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
/**
參數:
queue -隊列名稱
exchange -交易所名稱
routingKey -用於綁定的路由鍵
*/
channel.queueBind(queueName, exchangeName, routingKey);
//5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
/**
參數:
queue -隊列名稱
autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
callback -消費者對象的接口
*/
boolean autoAck = true;
channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
/**
參數:
consumerTag-與消費者相關聯的消費者標籤
envelope -消息的打包數據
properties -消息的內容頭數據
body -消息正文(客戶端特定的不透明字節數組)
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("------------consumer message-----------");
System.out.println("sonsumerTag:" + consumerTag);
System.out.println("envelope:" + envelope);
System.out.println("properties:" + properties);
System.out.println("msg:" + new String(body));
}
});
}
}
4、Topic Exchange交換機
主題交換機,這個交換機其實跟直連交換機流程差不多,但是它的特點就是在它的路由鍵和綁定鍵之間是有規則的。消息攜帶的路由鍵與隊列名屬於模糊匹配。
簡單地介紹下規則:
* (星號) 用來表示一個單詞 (必須出現的)
# (井號) 用來表示任意數量(零個或多個)單詞
1)消息生產者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4.發佈消息
String exchangeName="test_topic_exchange";
String routingKey = "test.topic";
String msg="hello rabbitmq consumer message - test_topic_exchange";
for (int i = 0; i < 2; i++) {
/**
參數:
exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
routingKey -路由鍵
mandatory -如果要設置“強制性”標誌,則爲true
props -消息的其他屬性-路由標頭等
body -消息正文
*/
channel.basicPublish(exchangeName, routingKey, false, null, msg.getBytes());
}
//5.釋放資源
channel.close();
connection.close();
}
}
2)消息消費者
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4. 創建消息隊列和direct交換機,並通過channel讓交換機跟消息隊列進行綁定
String queueName = "test_queue";
String queueName2 = "test_queue2";
String exchangeName ="test_topic_exchange";
String exchangeType = "topic";
String routingKey = "test.#";
String routingKey2 = "*.topic";
/**
參數:
queue -隊列名稱
durable -如果我們聲明一個持久隊列,則爲true(該隊列將在服務器重啓後保留下來)
exclusive -如果我們聲明一個排他隊列,則爲true(僅限此連接)
autoDelete -如果我們聲明一個自動刪除隊列,則爲true(服務器將在不再使用它時將其刪除)
arguments -隊列的其他屬性(構造參數)
*/
channel.queueDeclare(queueName, true, false, false, null);
channel.queueDeclare(queueName2, true, false, false, null);
/**
參數:
exchange -交易所名稱
type -交易所類型
durable -如果我們聲明持久交換,則爲true(該交換將在服務器重啓後保留下來)
autoDelete -如果服務器在不再使用交換機時應刪除該交換機,則爲true
arguments -用於交換的其他屬性(構造參數)
*/
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
/**
參數:
queue -隊列名稱
exchange -交易所名稱
routingKey -用於綁定的路由鍵
*/
channel.queueBind(queueName, exchangeName, routingKey);
channel.queueBind(queueName2, exchangeName, routingKey2);
//5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
/**
參數:
queue -隊列名稱
autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
callback -消費者對象的接口
*/
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, new DefaultConsumer(channel) {
/**
參數:
consumerTag-與消費者相關聯的消費者標籤
envelope -消息的打包數據
properties -消息的內容頭數據
body -消息正文(客戶端特定的不透明字節數組)
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("------------consumer message-----------");
System.out.println("sonsumerTag:" + consumerTag);
System.out.println("envelope:" + envelope);
System.out.println("properties:" + properties);
System.out.println("msg:" + new String(body));
}
});
}
}
創建了兩個隊列,這是隻對一個隊列的消息進行了消費
5、Fanout Exchange交換機
扇型交換機,這個交換機沒有路由鍵概念,就算你綁了路由鍵也是無視的,不處理的。 這個交換機在接收到消息後,會直接轉發到綁定到它上面的所有隊列。
1)消息生產者
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4.發佈消息
String exchangeName="test_fanout_exchange";
String routingKey = "";
String msg="hello rabbitmq consumer message - test_fanout_exchange";
for (int i = 0; i < 2; i++) {
/**
參數:
exchange -將消息發佈到的交換機, 若爲空字符串時,使用默認的交換機
routingKey -路由鍵
mandatory -如果要設置“強制性”標誌,則爲true
props -消息的其他屬性-路由標頭等
body -消息正文
*/
channel.basicPublish(exchangeName, routingKey, false, null, msg.getBytes());
}
//5.釋放資源
channel.close();
connection.close();
}
}
2)消息消費者
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) throws Exception {
//1.創建工廠類
ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setVirtualHost("/");
//默認情況下爲“ guest” /“ guest”,僅限本地主機連接
factory.setUsername("guest");
factory.setPassword("guest");
//2.通過工廠創建connection
Connection connection = factory.newConnection();
//3.創建channel對象
Channel channel = connection.createChannel();
//4. 創建消息隊列和direct交換機,並通過channel讓交換機跟消息隊列進行綁定
String queueName = "test_queue";
String queueName2 = "test_queue2";
String exchangeName ="test_fanout_exchange";
String exchangeType = "fanout";
String routingKey = "test.#"; // 隨便寫,扇形交換機不處理路右鍵
String routingKey2 = "1*";
/**
參數:
queue -隊列名稱
durable -如果我們聲明一個持久隊列,則爲true(該隊列將在服務器重啓後保留下來)
exclusive -如果我們聲明一個排他隊列,則爲true(僅限此連接)
autoDelete -如果我們聲明一個自動刪除隊列,則爲true(服務器將在不再使用它時將其刪除)
arguments -隊列的其他屬性(構造參數)
*/
channel.queueDeclare(queueName, true, false, false, null);
channel.queueDeclare(queueName2, true, false, false, null);
/**
參數:
exchange -交易所名稱
type -交易所類型
durable -如果我們聲明持久交換,則爲true(該交換將在服務器重啓後保留下來)
autoDelete -如果服務器在不再使用交換機時應刪除該交換機,則爲true
arguments -用於交換的其他屬性(構造參數)
*/
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
/**
參數:
queue -隊列名稱
exchange -交易所名稱
routingKey -用於綁定的路由鍵
*/
channel.queueBind(queueName, exchangeName, routingKey);
channel.queueBind(queueName2, exchangeName, routingKey2);
//5.通過channel把消費者和消息隊列進行關聯,獲取消息進行處理
/**
參數:
queue -隊列名稱
autoAck-如果服務器應考慮一旦傳遞已確認的消息,則爲true;如果服務器應該期望顯式確認,則返回false
callback -消費者對象的接口
*/
boolean autoAck = false;
channel.basicConsume(queueName2, autoAck, new DefaultConsumer(channel) {
/**
參數:
consumerTag-與消費者相關聯的消費者標籤
envelope -消息的打包數據
properties -消息的內容頭數據
body -消息正文(客戶端特定的不透明字節數組)
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("------------consumer message-----------");
System.out.println("sonsumerTag:" + consumerTag);
System.out.println("envelope:" + envelope);
System.out.println("properties:" + properties);
System.out.println("msg:" + new String(body));
}
});
}
}
一開始,我對RabbitMQ一點都不瞭解,但是項目中需要用到RabbitMQ,查資料算是springboot使用RabbitMQ實現了項目中的業務,但是,知其然不知其所以然,所以,從頭紮實的瞭解下RabbitMQ
RabbitMQ 不同交換機的處理機制,也算入門了,交換機的創建不同,其他基本類似,剛開始先會使用即可。
站在前輩的肩膀上,每天進步一點點
ends~