目錄java
RabbitMQ是一個消息代理:它接受並轉發消息。 您能夠將其視爲郵局:當您將要把寄發的郵件投遞到郵箱中時,您能夠確信Postman 先生最終會將郵件發送給收件人。 在這個比喻中,RabbitMQ是一個郵箱,郵局和郵遞員,用來接受,存儲和轉發二進制數據塊的消息。程序員
隊列就像是在RabbitMQ中扮演郵箱的角色。 雖然消息通過RabbitMQ和應用程序,但它們只能存儲在隊列中。 隊列只受主機的內存和磁盤限制的限制,它本質上是一個大的消息緩衝區。 許多生產者能夠發送到一個隊列的消息,許多消費者能夠嘗試從一個隊列接收數據。spring
producer即爲生產者,用來產生消息發送給隊列。consumer是消費者,須要去讀隊列內的消息。producer,consumer和broker(rabbitMQ server)沒必要駐留在同一個主機上;確實在大多數應用程序中它們是這樣分佈的。json
簡單隊列是最簡單的一種模式,由生產者、隊列、消費者組成。生產者將消息發送給隊列,消費者從隊列中讀取消息完成消費。服務器
在下圖中,「P」是咱們的生產者,「C」是咱們的消費者。 中間的框是隊列 - RabbitMQ表明消費者的消息緩衝區。app
package com.anqi.mq.nat; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; 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(); //實際場景中,消息多爲json格式的對象 String msg = "hello"; //4. 發送三條數據 for (int i = 1; i <= 3 ; i++) { channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); System.out.println("Send message" + i +" : " + msg); } //5. 關閉鏈接 channel.close(); connection.close(); } }
/** * Declare a queue * @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; /** * Publish a message * @see com.rabbitmq.client.AMQP.Basic.Publish * @param exchange the exchange to publish the message to * @param routingKey the routing key * @param props other properties for the message - routing headers etc * @param body the message body * @throws java.io.IOException if an error is encountered */ void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException; /** * Start a non-nolocal, non-exclusive consumer, with * a server-generated consumerTag. * @param queue the name of the queue * @param autoAck true if the server should consider messages * acknowledged once delivered; false if the server should expect * explicit acknowledgements * @param callback an interface to the consumer object * @return the consumerTag generated by the server * @throws java.io.IOException if an error is encountered * @see com.rabbitmq.client.AMQP.Basic.Consume * @see com.rabbitmq.client.AMQP.Basic.ConsumeOk * @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer) */ String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
package com.anqi.mq.nat; import com.rabbitmq.client.*; import java.io.IOException; 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. 聲明一個隊列 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"); System.out.println(" [x] Received '" + message + "'"); } }; //6. 設置 Channel 消費者綁定隊列 channel.basicConsume(QUEUE_NAME, true, consumer); } }
Send message1 : hello Send message2 : hello Send message3 : hello [*] Waiting for messages. To exit press CTRL+C [x] Received 'hello' [x] Received 'hello' [x] Received 'hello'
當咱們啓動生產者以後查看RabbitMQ管理後臺能夠看到有一條消息正在等待被消費。
當咱們啓動消費者以後再次查看,能夠看到積壓的一條消息已經被消費。
ide
隊列聲明queueDeclare的參數:第一個參數表示隊列名稱、第二個參數爲是否持久化(true表示是,隊列將在服務器重啓時生存)、第三個參數爲是不是獨佔隊列(建立者可使用的私有隊列,斷開後自動刪除)、第四個參數爲當全部消費者客戶端鏈接斷開時是否自動刪除隊列、第五個參數爲隊列的其餘參數。測試
basicConsume的第二個參數autoAck: 應答模式,true:自動應答,即消費者獲取到消息,該消息就會從隊列中刪除掉,false:手動應答,當從隊列中取出消息後,須要程序員手動調用方法應答,若是沒有應答,該消息還會再放進隊列中,就會出現該消息一直沒有被消費掉的現象。this
這種簡單隊列的模式,系統會爲每一個隊列隱式地綁定一個默認交換機,交換機名稱爲" (AMQP default)",類型爲直連 direct,當你手動建立一個隊列時,系統會自動將這個隊列綁定到一個名稱爲空的 Direct 類型的交換機上,綁定的路由鍵 routing key 與隊列名稱相同,至關於channel.queueBind(queue:"QUEUE_NAME", exchange:"(AMQP default)「, routingKey:"QUEUE_NAME");
雖然實例沒有顯式聲明交換機,可是當路由鍵和隊列名稱同樣時,就會將消息發送到這個默認的交換機中。這種方式比較簡單,可是沒法知足複雜的業務需求,因此一般在生產環境中不多使用這種方式。3d
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 官方文檔
引入 Maven 依賴
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.6.0</version> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit</artifactId> <version>2.1.5.RELEASE</version> </dependency>
spring 配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit" xsi:schemaLocation="http://www.springframework.org/schema/rabbit https://www.springframework.org/schema/rabbit/spring-rabbit.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <rabbit:connection-factory id="connectionFactory" host="localhost" virtual-host="/" username="guest" password="guest"/> <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/> <rabbit:admin connection-factory="connectionFactory"/> <rabbit:queue name="MY-QUEUE"/> </beans>
使用測試
import org.springframework.amqp.core.AmqpTemplate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext app = new ClassPathXmlApplicationContext("spring/rabbit-context.xml"); AmqpTemplate amqpTemplate = app.getBean(AmqpTemplate.class); amqpTemplate.convertAndSend("MY-QUEUE", "Item"); String msg = (String) amqpTemplate.receiveAndConvert("MY-QUEUE"); System.out.println(msg); } }
參考方法
/** * Convert a Java object to an Amqp {@link Message} and send it to a specific exchange * with a specific routing key. * * @param exchange the name of the exchange * @param routingKey the routing key * @param message a message to send * @throws AmqpException if there is a problem */ void convertAndSend(String exchange, String routingKey, Object message) throws AmqpException; /** * Receive a message if there is one from a specific queue and convert it to a Java * object. Returns immediately, possibly with a null value. * * @param queueName the name of the queue to poll * @return a message or null if there is none waiting * @throws AmqpException if there is a problem */ @Nullable Object receiveAndConvert(String queueName) throws AmqpException;