一、簡介
RabbitMQ是一個開源的AMQP實現,服務器端用Erlang語言編寫,支持多種客戶端。用於在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。
二、RabbitMQ核心概念
Producer(生產者):就是投遞消息的一方。生產者負責生產消息,然後發佈到RabbitMQ中。其中消息一般分爲兩個部分:消息體和附加消息。
消息體(payload):在實際應用中,消息體一般是一個帶有業務邏輯結構的數據,比如一個JSON字符串。然後可以進一步對這個消息體進行序列化操作。
附加信息:用來表述這條消息,比如目標交換器的名稱、路由鍵和一些自定義屬性等等。
Broker:消息中間件的服務節點。對於RabbitMQ來說,一個RabbitMQ Broker可以簡單地看作一個RabbitMQ服務節點,或者RabbitMQ服務實例。也可以將一個RabbitMQ Broker看作一臺RabbitMQ服務器。
Virtual Host(虛擬主機):表示一批交換器、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。一個Broker中可以有多個Virtual Host;就好比一個mysql-server中可以有多個database。每個vhost本質上就是一個mini版的RabbitMQ服務器,擁有自己的隊列、交換器、綁定和權限機制。vhost是AMQP概念的基礎,必須在連接時指定,RabbitMQ默認的vhost是 /。
Channel:頻道或信道,是建立在Connection連接之上的一種輕量級的連接。大部分的操作是在Channel這個接口中完成的,包括定義隊列的聲明queueDeclare、交換機的聲明exchangeDeclare、隊列的綁定queueBind、發佈消息basicPublish、消費消息basicConsume等。如果把Connection比作一條光線電纜的話,那麼Channel信道就是光線電纜中的其中一束光纖。一個Connection上可以創建任意數量的Channel。
RoutingKey(路由鍵):生產者將消息發給交換器的時候,一般會指定一個RoutingKey,用來指定這個消息的路由規則。RoutingKey需要與交換器類型和綁定鍵(BindingKey)聯合使用,在交換器類型和綁定鍵(BindingKey)固定的情況下,生產者可以在發送消息給交換器時,通過指定RoutingKey來決定消息流向哪裏。
Exchange(交換器):生產者將消息發送到Exchange(交換器,通常也可以用大寫的“X”來表示),由交換器將消息路由到一個或者多個隊列中,如果路由不到,或返回給生產者,或直接丟棄。
RabbitMQ常見的交換器有fanout、direct、topic、headers這四種:
fanout:扇形交換機,它會把所有發送到該交換器的消息路由到所有與該交換器綁定的隊列中。
direct:直連交換機,它會把消息路由到那些BindingKey和RoutingKey完全匹配的隊列中。
topic:主題交換機,與direct類似,但它可以通過通配符進行模糊匹配(其中*代表匹配一個單詞,#表示一串匹配的字段)。
headers:頭交換機,不依賴於路由鍵的匹配規則來路由消息,而是根據發送的消息內容的headers屬性進行匹配。headers類型的交換器性能很差,一般不使用。
Queue(隊列):是RabbitMQ的內部對象用於存儲消息。
Binding(綁定):RabbitMQ中通過綁定將交換器與隊列關聯起來,在綁定的時候一般會指定一個綁定鍵(BindingKey),這樣RabbitMQ就知道正確地將消息路由到隊列了。
Cosumer(消費者):就是接收消息的一方。消費者連接到RabbitMQ服務器,並訂閱到隊列上。當消費者消費一條消息時,只有消費消息的消息體(payload)。在消息路由的過程中,消息的標籤會丟棄,存入到隊列中的消息只有消息體,消費者也只會消費到消息體,也就是不知道消息的生產者是誰,當然消費者也不需要知道。
三、RabbitMQ運作流程
生產者發送消息的過程:
-
生產者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)。
-
生產者聲明一個交換器,並設置相關屬性,比如交換機類型、是否持久化等。
-
生產者聲明一個隊列並設置相關屬性,比如是否排他、是否持久化、是否自動刪除等。
-
生產者通過路由鍵將交換器和隊列綁定起來。
-
生產者發送消息至RabbitMQ Broker,其中包含路由鍵、交換器等信息。
-
相應的交換器根據接收到的路由鍵查找相匹配的隊列。
-
如果找到,則將從生產者發送過來的消息存入相應的隊列中。
-
如果沒有找到,則根據生產者配置的屬性選擇丟棄還是回退給生產者。
-
關閉信道、關閉連接。
代碼實例:
public static void main(String[] args) {
// 1、創建連接工廠
ConnectionFactory factory = new ConnectionFactory();
// 2、設置連接屬性
factory.setHost("192.168.100.000");
factory.setUsername("***");
factory.setPassword("***");
Connection connection = null;
Channel channel = null;
try {
// 3、從連接工廠獲取連接
connection = factory.newConnection("生產者");
// 4、從鏈接中創建通道
channel = connection.createChannel();
// 定義fanout類型的交換器
channel.exchangeDeclare("ps_test", "fanout");
// 消息內容
String message = "Hello Publish";
// 發送消息到ps_test交換器上
channel.basicPublish("ps_test", "", null, message.getBytes());
System.out.println("消息 " + message + " 已發送!");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7、關閉通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 8、關閉連接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
消費者接收消息的過程:
-
生產者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)
-
消費者向RabbitMQ Broker請求消費相應隊列中的消息,可能會設置相應的回調函數,以及做一些準備工作。
-
等待RabbitMQ Broker迴應並投遞相應隊列中的消息,消費者接收消息。
-
消費者確認(ack)接收到的消息。
-
RabbitMQ從隊列中刪除相應已經被確認的消息。
-
關閉信道、關閉連接。
代碼實例:
public static void main(String[] args) {
// 1、創建連接工廠
ConnectionFactory factory = new ConnectionFactory();
// 2、設置連接屬性
factory.setHost("192.168.100.000");
factory.setUsername("***");
factory.setPassword("***");
Connection connection = null;
Channel channel = null;
try {
// 3、從連接工廠獲取連接
connection = factory.newConnection("消費者");
// 4、從鏈接中創建通道
channel = connection.createChannel();
/**
* 5、聲明(創建)隊列
* 如果隊列不存在,纔會創建
* RabbitMQ 不允許聲明兩個隊列名相同,屬性不同的隊列,否則會報錯
*
* queueDeclare參數說明:
* @param queue 隊列名稱
* @param durable 隊列是否持久化
* @param exclusive 是否排他,即是否爲私有的,如果爲true,會對當前隊列加鎖,其它通道不能訪問,
* 並且在連接關閉時會自動刪除,不受持久化和自動刪除的屬性控制。
* 一般在隊列和交換器綁定時使用
* @param autoDelete 是否自動刪除,當最後一個消費者斷開連接之後是否自動刪除
* @param arguments 隊列參數,設置隊列的有效期、消息最大長度、隊列中所有消息的生命週期等等
*/
channel.queueDeclare("queue1", false, false, false, null);
// 6、定義收到消息後的回調
DeliverCallback callback = new DeliverCallback() {
public void handle(String consumerTag, Delivery message) throws IOException {
System.out.println("收到消息:" + new String(message.getBody(), "UTF-8"));
}
};
// 7、監聽隊列
channel.basicConsume("queue1", true, callback, new CancelCallback() {
public void handle(String consumerTag) throws IOException {
}
});
System.out.println("開始接收消息");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 8、關閉通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
// 9、關閉連接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
歡迎關注本人公衆號,一起討論學習