一個最簡單的消息隊列,帶你理解 RabbitMQ!

Java技術棧

www.javastack.cn

關注閱讀更多優質文章



作者:海向
來源:www.cnblogs.com/haixiang/p/10826710.html

RabbitMQ 簡述

RabbitMQ是一個消息代理:它接受並轉發消息。您可以將其視爲郵局:當您將要把寄發的郵件投遞到郵箱中時,您可以確信Postman 先生最終會將郵件發送給收件人。在這個比喻中,RabbitMQ是一個郵箱,郵局和郵遞員,用來接受,存儲和轉發二進制數據塊的消息。

隊列就像是在RabbitMQ中扮演郵箱的角色。雖然消息經過RabbitMQ和應用程序,但它們只能存儲在隊列中。隊列只受主機的內存和磁盤限制的限制,它本質上是一個大的消息緩衝區。許多生產者可以發送到一個隊列的消息,許多消費者可以嘗試從一個隊列接收數據。

producer即爲生產者,用來產生消息發送給隊列。consumer是消費者,需要去讀隊列內的消息。producer,consumer和broker(rabbitMQ server)不必駐留在同一個主機上;確實在大多數應用程序中它們是這樣分佈的。

簡單隊列

簡單隊列是最簡單的一種模式,由生產者、隊列、消費者組成。生產者將消息發送給隊列,消費者從隊列中讀取消息完成消費。關注公衆號Java技術棧回覆:面試,可以獲取我整理的 RabbitMQ 系列面試題,帶全部答案。

在下圖中,“P”是我們的生產者,“C”是我們的消費者。中間的框是隊列 - RabbitMQ代表消費者的消息緩衝區。

Java 方式

生產者

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, truefalsefalsenull);
        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管理後臺可以看到有一條消息正在等待被消費。



當我們啓動消費者之後再次查看,可以看到積壓的一條消息已經被消費。


總結

隊列聲明queueDeclare的參數:第一個參數表示隊列名稱、第二個參數爲是否持久化(true表示是,隊列將在服務器重啓時生存)、第三個參數爲是否是獨佔隊列(創建者可以使用的私有隊列,斷開後自動刪除)、第四個參數爲當所有消費者客戶端連接斷開時是否自動刪除隊列、第五個參數爲隊列的其他參數。

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 官方文檔

spring-amqp方式

引入 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 Boot 學習教程:

https://github.com/javastacks/spring-boot-best-practice

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;






關注Java技術棧看更多幹貨



戳原文,獲取精選面試題!

本文分享自微信公衆號 - Java技術棧(javastack)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章