RabbitMQ學習系列:三、發佈/訂閱

上一篇記錄了一個簡單的rabbitmq 發佈接收隊列消息,但沒有使用路由。本篇寫一寫rabbitmq的路由的使用。

介紹

有幾個概念介紹一下
1、生產者

生產者是發送消息的用戶的應用程序

2、路由

處理生產者消息發到哪個隊列

3、隊列

隊列是存儲消息的緩衝器

4、消費者

消費者是接收消息的用戶的應用程序

消息模型

RabbitMQ中的消息傳遞模型的核心思想是生產者永遠不會將任何消息直接發送到隊列中。實際上,生產者通常甚至不知道消息是否會被傳送到任何隊列中。我們的消息實際上是從生產者傳遞到路由,路由會綁定隊列並指定綁定的routingKey。根據routingkey匹配到這個路由上綁定的隊列,並向隊列發送消息,不能匹配上的隊列則不會收到該消息。所以我們上一篇文章中雖然沒有明確定義路由,實際上是使用是默認的路由。我們可以根據需求自己聲明相應的路由。

路由(Exchange)

聲明方式

exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments)

exchange :路由名
type : 路由類型
durable: 是否持久化
autoDelete:是否自動刪除
arguments: 其它參數

類型

1、Fanout

廣播。這個類型的路由會忽略routingKey。收到生產者消息後會直接發送給綁定在該路由上的所有隊列

2、Direct

單播。該類型的路由會根據routingKey去匹配隊列,將消息發送給該路由上綁定的並且routingKey完全匹配上的隊列

3、Topic

多播。該類型路由的routingKey可以使用通配符進行匹配。即 * 代表一個單詞,# 代表多個單詞。routingKey 的定義不能是任意字符,只能是由點號分隔的字符串,如: “ stock.usd.nyse ”,“ nyse.vmw ”,“ quick.orange.rabbit ”。
如下圖
Q1隊列的與路由綁定的routingKey 是*.orange.*
Q2隊列的與路由綁定的routingKey 是 *.*.rabbit 與 lazy.#
如果我們發送消息時指定的routingKey爲:quick.orange.rabbit ,則消息會被路由發送到Q1與Q2兩個隊列中。
如果我們發送消息時指定的routingKey爲:lazy.brown.fox ,則消息會被路由發送到Q2隊列
如果指定的routingKey爲 lazy.pink.rabbit,也會被髮送到Q2,但只會發送一次,即使它匹配到了兩個綁定的routingKey
示例

4、Headers

這種類型的路由不處理路由鍵,而是根據發送消息的Headers屬性進行匹配,在隊列綁定交換機的時候會指定一組鍵對值;

綁定隊列

queueBind(String queue, String exchange, String routingKey)

前面我們說過 RabbitMQ中的消息傳遞模型的核心思想是生產者永遠不會將任何消息直接發送到隊列中。所以我們每個隊列都需要綁定在一個路由上。從生產者發送到該路由的消息只會被傳遞到與路由綁定的隊列上。綁定時還需要指定一個routingKey,路由根據發佈消息時傳遞過來的routintKey來匹配到相應隊列並傳送消息到該隊列中。
queue: 隊列名
exchange: 路由名
routingKey: 隊列與路由綁定的key

編寫生產者

public class EmitLogTopic {
    private final static String EXCHANGE_NAME="topic_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        String message ="Hello World! This is error info!";
        /* 我們使用的是topic類型的路由,第二個參數爲routingKey*/
        channel.basicPublish(EXCHANGE_NAME,"rabbit.log.error",null,message.getBytes());

        channel.close();
        connection.close();
    }
}

發送消息時我們指定的routingKey爲 rabbit.log.error 因爲是topic類型的路由,所以需要用點分隔的形式寫routingKey ,如果是Direct類型則不需要。

編寫消費者

public class ReceivedLogsTopic {
    private final static String EXCHANGE_NAME="topic_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        String queuesName = channel.queueDeclare().getQueue();
        /*將隊列綁定路由並定義topic類型路由的匹配規則*/
        channel.queueBind(queuesName,EXCHANGE_NAME,"*.log.*");

        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] recv:" + message);
            }
        };
        channel.basicConsume(queuesName,true,consumer);
    }
}

這裏隊列綁定topic 類型路由時指定的匹配規則爲 *.log.*
前面生產者發送時的routingKey 爲 rabbit.log.error 所以這條消息會被路由監聽到。 如果我們將綁定的匹配規則修改爲 log.# 並重新啓動一個消費者B,通過生產者再次發送消息,則消費者B不會監聽到這條消息,因爲routingKey 無法匹配上,路由不會把這條消息傳遞給消費者B

* 注意在測試時是需要先啓動消費者,再啓動生產者。因爲如果沒有消費者在線,消息會被rabbitMq丟棄處理

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