RabbitMQ 簡述
RabbitMQ是一個消息代理:它接受並轉發消息。 您可以將其視爲郵局:當您將要把寄發的郵件投遞到郵箱中時,您可以確信Postman 先生最終會將郵件發送給收件人。 在這個比喻中,RabbitMQ是一個郵箱,郵局和郵遞員,用來接受,存儲和轉發二進制數據塊的消息。
隊列就像是在RabbitMQ中扮演郵箱的角色。 雖然消息經過RabbitMQ和應用程序,但它們只能存儲在隊列中。 隊列只受主機的內存和磁盤限制的限制,它本質上是一個大的消息緩衝區。 許多生產者可以發送到一個隊列的消息,許多消費者可以嘗試從一個隊列接收數據。
producer即爲生產者,用來產生消息發送給隊列。consumer是消費者,需要去讀隊列內的消息。producer,consumer和broker(rabbitMQ server)不必駐留在同一個主機上;確實在大多數應用程序中它們是這樣分佈的。
簡單隊列
簡單隊列是最簡單的一種模式,由生產者、隊列、消費者組成。生產者將消息發送給隊列,消費者從隊列中讀取消息完成消費。
在下圖中,“P”是我們的生產者,“C”是我們的消費者。 中間的框是隊列 - RabbitMQ代表消費者的消息緩衝區。
java 方式
生產者
public class MyProducer {
private static final String QUEUE_NAME = "ITEM_QUEUE";
public static void main(String[] args) throws Exception {
// 1. 創建一個 ConnectionFactory 並進行設置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
// 2. 通過連接工廠來創建連接
Connection connection = factory.newConnection();
// 3. 通過 Connection 來創建 Channel
Channel channel = connection.createChannel();
/* Message 消息 Properties
* deliverMode 設置爲 2 的時候代表持久化消息
* expiration 意思是設置消息的有效期,超過10秒沒有被消費者接收後會被自動刪除
* headers 自定義的一些屬性
*/
Map<String, Object> headers = new HashMap<>();
headers.put("myHead1", "111");
headers.put("myHead2", "222");
AMQP.BasicProperties properties =
new AMQP.BasicProperties()
.builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.expiration("100000")
.headers(headers)
.build();
// 實際場景中,消息多爲json格式的對象 Message:消息的 Body
String msg = "hello";
// 4. 發送三條數據
for (int i = 1; i <= 3; i++) {
channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());
System.out.println("Send message" + i + " : " + msg);
}
// 5. 關閉連接
channel.close();
connection.close();
}
}
消費者
public class MyConsumer {
private static final String QUEUE_NAME = "ITEM_QUEUE";
public static void main(String[] args) throws Exception {
// 1. 創建一個 ConnectionFactory 並進行設置
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
// 2. 通過連接工廠來創建連接
Connection connection = factory.newConnection();
// 3. 通過 Connection 來創建 Channel
Channel channel = connection.createChannel();
// 4. 聲明一個隊列
/* durable 持久化
* exclusive 排他隊列
* 如果你想創建一個只有自己可見的隊列,即不允許其它用戶訪問,RabbitMQ允許你將一個Queue聲明成爲排他性的(Exclusive Queue)。
* 該隊列的特點是:
** 只對首次聲明它的連接(Connection)可見
** 會在其連接斷開的時候自動刪除。
** 對於第一點,首先是強調首次聲明,因爲另外一個連接無法聲明一個同樣的排他性隊列;其次是隻區別連接(Connection)而不是通道(Channel),從同一個連接創建的不同的通道可以同時訪問某一個排他性的隊列。這裏說的連接是指一個AMQPConnection,以RabbitMQ的Java客戶端爲例:
** 如果試圖在一個不同的連接中重新聲明或訪問(如publish,consume)該排他性隊列,會得到資源被鎖定的錯誤:
** `ESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'UserLogin2'`
** 對於第二點,RabbitMQ會自動刪除這個隊列,而不管這個隊列是否被聲明成持久性的(Durable =true)。 也就是說即使客戶端程序將一個排他性的隊列聲明成了Durable的,只要調用了連接的Close方法或者客戶端程序退出了,RabbitMQ都會刪除這個隊列。注意這裏是連接斷開的時候,而不是通道斷開。這個其實前一點保持一致,只區別連接而非通道。
* */
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
/*
true:表示自動確認,只要消息從隊列中獲取,無論消費者獲取到消息後是否成功消費,都會認爲消息已經成功消費
false:表示手動確認,消費者獲取消息後,服務器會將該消息標記爲不可用狀態,等待消費者的反饋,如果消費者一
直沒有反饋,那麼該消息將一直處於不可用狀態,並且服務器會認爲該消費者已經掛掉,不會再給其發送消息,
直到該消費者反饋。
*/
// 5. 創建消費者並接收消息
Consumer consumer =
new DefaultConsumer(channel) {
@Override
public void handleDelivery(
String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
Map<String, Object> headers = properties.getHeaders();
System.out.println("head: " + headers.get("myHead1"));
System.out.println(" [x] Received '" + message + "'");
System.out.println("expiration : " + properties.getExpiration());
}
};
// 6. 設置 Channel 消費者綁定隊列
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
運行結果
## MyProducer
Send message1 : hello
Send message2 : hello
Send message3 : hello
## MyConsumer
[*] Waiting for messages. To exit press CTRL+C
head: 111
[x] Received 'hello'
expiration : 100000
head: 111
[x] Received 'hello'
expiration : 100000
head: 111
[x] Received 'hello'
expiration : 100000
當我們啓動生產者之後查看RabbitMQ管理後臺可以看到有一條消息正在等待被消費(需要先手動創建隊列或者啓動一下消費者)。
當我們啓動消費者之後再次查看,可以看到積壓的一條消息已經被消費。
總結
- 聲明交換機
/**
* Declare an exchange, via an interface that allows the complete set of
* arguments.
* @see com.rabbitmq.client.AMQP.Exchange.Declare
* @see com.rabbitmq.client.AMQP.Exchange.DeclareOk
* @param exchange the name of the exchange
* @param type the exchange type
* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
* @param autoDelete true if the server should delete the exchange when it is no longer in use
* @param internal true if the exchange is internal, i.e. can't be directly
* published to by a client.
* @param arguments other properties (construction arguments) for the exchange
* @return a declaration-confirm method to indicate the exchange was successfully declared
* @throws java.io.IOException if an error is encountered
*/
Exchange.DeclareOk exchangeDeclare(String exchange,
String type,boolean durable,
boolean autoDelete,boolean internal,
Map<String, Object> arguments) throws IOException;
-
Name: 交換機名稱
-
Type: 交換機類型direct、topic、 fanout、 headers
-
Durability: 是否需要持久化,true爲持久化
-
Auto Delete: 當最後一個綁定到Exchange. 上的隊列刪除後,自動刪除該Exchange
-
Internal: 當前Exchange是否用於RabbitMQ內部使用,默認爲False
-
Arguments: 擴展參數,用於擴展AMQP協議自制定化使用
-
隊列聲明
/**
* Declare a queue
* @see com.rabbitmq.client.AMQP.Queue.Declare
* @see com.rabbitmq.client.AMQP.Queue.DeclareOk
* @param queue the name of the queue
* @param durable true if we are declaring a durable queue (the queue will survive a server restart)
* @param exclusive true if we are declaring an exclusive queue (restricted to this connection)
* @param autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use)
* @param arguments other properties (construction arguments) for the queue
* @return a declaration-confirm method to indicate the queue was successfully declared
* @throws java.io.IOException if an error is encountered
*/
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) throws IOException;
方法 queueDeclare
的參數:第一個參數表示隊列名稱、第二個參數爲是否持久化(true表示是,隊列將在服務器重啓時生存)、第三個參數爲是否是獨佔隊列(創建者可以使用的私有隊列,斷開後自動刪除)、第四個參數爲當所有消費者客戶端連接斷開時是否自動刪除隊列、第五個參數爲隊列的其他參數。
第三個參數(exclusive)
排他隊列
如果你想創建一個只有自己可見的隊列,即不允許其它用戶訪問,RabbitMQ允許你將一個Queue聲明成爲排他性的(Exclusive Queue)。該隊列的特點是:
** 只對首次聲明它的連接(Connection)可見
** 會在其連接斷開的時候自動刪除。
** 對於第一點,首先是強調首次聲明,因爲另外一個連接無法聲明一個同樣的排他性隊列;其次是隻區別連接(Connection)而不是通道(Channel),從同一個連接創建的不同的通道可以同時訪問某一個排他性的隊列。這裏說的連接是指一個AMQPConnection,以RabbitMQ的Java客戶端爲例:
** 如果試圖在一個不同的連接中重新聲明或訪問(如publish,consume)該排他性隊列,會得到資源被鎖定的錯誤:
**ESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'UserLogin2'
** 對於第二點,RabbitMQ會自動刪除這個隊列,而不管這個隊列是否被聲明成持久性的(Durable =true)。 也就是說即使客戶端程序將一個排他性的隊列聲明成了Durable的,只要調用了連接的Close方法或者客戶端程序退出了,RabbitMQ都會刪除這個隊列。注意這裏是連接斷開的時候,而不是通道斷開。這個其實前一點保持一致,只區別連接而非通道。
-
basicConsume的第二個參數autoAck: 應答模式,true:自動應答,即消費者獲取到消息,該消息就會從隊列中刪除掉,false:手動應答,當從隊列中取出消息後,需要程序員手動調用方法應答,如果沒有應答,該消息還會再放進隊列中,就會出現該消息一直沒有被消費掉的現象。
-
這種簡單隊列的模式,系統會爲每個隊列隱式地綁定一個默認交換機,交換機名稱爲" (AMQP default)",類型爲直連 direct,當你手動創建一個隊列時,系統會自動將這個隊列綁定到一個名稱爲空的 Direct 類型的交換機上,綁定的路由鍵 routing key 與隊列名稱相同,相當於
channel.queueBind(queue:"QUEUE_NAME", exchange:"(AMQP default)“, routingKey:"QUEUE_NAME");
雖然實例沒有顯式聲明交換機,但是當路由鍵和隊列名稱一樣時,就會將消息發送到這個默認的交換機中。這種方式比較簡單,但是無法滿足複雜的業務需求,所以通常在生產環境中很少使用這種方式。 -
The default exchange is implicitly bound to every queue, with a routing key equal to the queue name. It is not possible to explicitly bind to, or unbind from the default exchange. It also cannot be deleted.默認交換機隱式綁定到每個隊列,其中路由鍵等於隊列名稱。不可能顯式綁定到,或從缺省交換中解除綁定。它也不能被刪除。
——引自 RabbitMQ 官方文檔
-
聲明交換機
/**
* Declare an exchange, via an interface that allows the complete set of
* arguments.
* @see com.rabbitmq.client.AMQP.Exchange.Declare
* @see com.rabbitmq.client.AMQP.Exchange.DeclareOk
* @param exchange the name of the exchange
* @param type the exchange type
* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
* @param autoDelete true if the server should delete the exchange when it is no longer in use
* @param internal true if the exchange is internal, i.e. can't be directly
* published to by a client.
* @param arguments other properties (construction arguments) for the exchange
* @return a declaration-confirm method to indicate the exchange was successfully declared
* @throws java.io.IOException if an error is encountered
*/
Exchange.DeclareOk exchangeDeclare(String exchange,
String type,boolean durable,
boolean autoDelete,boolean internal,
Map<String, Object> arguments) throws IOException;
-
Name: 交換機名稱
-
Type: 交換機類型direct、topic、 fanout、 headers
-
Durability: 是否需要持久化,true爲持久化
-
Auto Delete: 當最後一個綁定到Exchange. 上的隊列刪除後,自動刪除該Exchange
-
Internal: 當前Exchange是否用於RabbitMQ內部使用,默認爲False
-
Arguments: 擴展參數,用於擴展AMQP協議自制定化使用
Spring Boot簡單整合
參考《RabbitMQ(四):RabbitMQ與Spring Boot簡單整合 快速嚐鮮版》
Spring Boot 版與原生的寫法相比不知道簡便的多少。但是原生寫法是基礎,我們也是需要了解的。