rabbitMQ二之工作模式

目錄

1.工具類抽取

2.簡單隊列

2.1消息生產者

2.2消息消費者

2.2.1客戶端自動應答

2.2.2客戶端手動應答

3.工作隊列

3.1消息生產者

3.2消息消費者

4.發佈訂閱

4.1消息生產者

4.2消息消費者

5.路由

5.1消息生產者

5.2消息消費者

6.話題

6.1消息生產者

6.2消息消費者

7.RPC

8.發佈者確認

8.1事務機制

8.2confirm機制

8.2.1同步確認機制

8.2.2異步確認機制


rabbitMQ提供一下幾種工作模式:

  1. 簡單隊列
  2. 工作隊列
  3. 發佈訂閱
  4. 路由
  5. 話題
  6. RPC
  7. 發佈者確認

1.工具類抽取

爲了方便代碼能直接使用,先將代碼中用到的公告部分抽取取來。這個工具欄的作用很簡單,就是回去一個Connection,代碼如下:

public class ConnectionUtil {

	public static Connection getConnection() throws Exception {
		// 定義連接工廠
		ConnectionFactory factory = new ConnectionFactory();
		// 設置服務地址
		factory.setHost("localhost");
		// 端口
		factory.setPort(5672);
		// 設置賬號信息,用戶名、密碼、vhost
		factory.setVirtualHost("testhost");
		factory.setUsername("admin");
		factory.setPassword("admin");
		// 通過工程獲取連接
		Connection connection = factory.newConnection();
		return connection;
	}
}

 

 

2.簡單隊列

簡單隊列,就如名字一樣,簡單到這個隊列,只有一個生產者,只對應一個消費者了。

2.1消息生產者

    public boolean sendMessage(String queueName, String message) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明(創建)隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 消息內容
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        // 關閉通道和連接
        channel.close();
        connection.close();
        return false;
    }

聲明隊列的時候,有四個參數,應爲後面經常出現,現在提前說明一下這四個參數的含義:

channel.queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments);
  1. queue:我們使用的隊列名稱
  2. durable:是否將消息持久化;假如我們在創建queue的時候沒有指定其持久化,後期不可以在代碼中修改其爲持久化隊列,只能刪除重建。rabbitmq不允許重新定義(不同參數)一個已存在的隊列
  3. exclusive:是否聲明爲獨佔隊列
  4. autoDelete:消息投遞到消費者後,server上是否自動刪除消息
  5. arguments:隊列的配置信息

2.2消息消費者

消費者消費信息的時候,對服務器有兩種應答方式:自動應答,手動應答

自動應答:當隊列服務器將消息投遞給消費者後,不管消費者是否成功處理,都自行將隊列刪除。

手動應答:當隊列服務器將消息投遞給消費者後,需要消費者確認是否在服務器上刪除該消息。

2.2.1客戶端自動應答

    /**
     * 客戶端自動應答消費消息
     */
    public void reciveByAutoMode(String queueName) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 定義消費者
        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("receiveMessage:" + message);
            }
        };
        // 監聽隊列
        channel.basicConsume(queueName, true, consumer);
    }

 代碼中監聽隊列的第二個參數設置爲true,表示自動應答。

basicConsume(String queue, boolean autoAck, Consumer callback)

2.2.2客戶端手動應答

    /**
     * 客戶端手動應答
     */
    public void reciveByHandleMode(String queueName) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 定義消費者
        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("receiveMessage:" + message);
                // 程序確認,而不是rabbit的自動確認(手動回執)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列,改成false則表示手動應答
        channel.basicConsume(queueName, false, consumer);
    }

手動應答,首先需要將監聽隊列的第二個參數設置爲false,

basicConsume(String queue, boolean autoAck, Consumer callback)

 同時,在consumer中,需要手動發送一個確認信息。

  channel.basicAck(envelope.getDeliveryTag(), false);

3.工作隊列

消息生產者可以不斷的向服務器發送消息,但是消費者不一定能夠及時或者快速處理掉消息,這就會導致消息的積壓。

工作隊列的出現,在某種程度上降低了消息積壓發生的可能性。

工作隊列,通過多個消費者消費同一個隊列的數據,加快了消息的處理速度。

同時,隊列提供了兩種消息消費類型:公平消費,輪訓消費

公平消費:服務器根據消費者的能力來投遞消息,在消費者沒有完成對消息的處理時,不會再次發送一條消息至消費者。

輪訓消費:服務器將消息輪訓投遞給消費者,如果消費者有一個處理能力較差,會導致最後的壓力都集中在能力差的機器上。

生產中,我們毫無疑問的提倡公平分發消費。

3.1消息生產者

    public boolean sendMessage2WorkQueue(String queueName, List<String> messages) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明(創建)隊列
        channel.queueDeclare(queueName, false, false, false, null);
        for (String message : messages) {
            //發送消息
            channel.basicPublish("", queueName, null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }

        // 此行代碼做公平分發用
        // 每個消費者發送確認消息之前,消息隊列不發送下一個消息到消費者,一次只處理一個消息
        // 限制發送給同一個消費者不得超過一條消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        // 關閉通道和連接
        channel.close();
        return true;
    }

3.2消息消費者

   /**
     * 客戶端手動應答
     * 當採用公平分發的時候,消費端要關閉自動應答,改爲手動應答
     *
     * @param queueName
     * @throws Exception
     */
    public void reciveByHandleMode(String queueName) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        final Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 定義消費者
        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("receiveMessage:" + message);
                // 程序確認,而不是rabbit的自動確認(手動回執)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列,改成false則表示手動應答
        channel.basicConsume(queueName, false, consumer);
    }

 

4.發佈訂閱

 

從這開始接觸一個新的概念:交換機

交換機相當於一個路由,對生產者發來的消息,根據配置進行路由,其本身並不存儲消息。在rabbitmq中,只有隊列能存儲消息。

路由的方式有三種:fanout(對應發佈訂閱模式),direct(對應路由模式),topic(對應主題模式)

  1. fanout:發佈訂閱模式,顧名思義,像扇子一樣(扇出),對綁定到該交換機的所有隊列,都能收到消息。此時routingKey不生效。
  2. direct:路由模式,對綁定到該交換機的隊列,通過routingKey匹配,只有完全匹配,交換機纔會將消息路由到該隊列。
  3. topic:主題模式,將路由和某個模式進行匹配(規則匹配, #:匹配一個或者多個; *:匹配一個)。匹配上的隊列,交換機纔會路由消息至該隊列

 


1.一個生產者,多個消費者
2. 每一個消費者都有自己的隊列 </br>
3. 生產者沒有直接把消息發送到隊列,而是發到了交換機(轉發器 exchange)
4. 每個隊列都要綁定到交換機上
5. 生產者發送的消息,經過交換機,到達隊列,就能實現一個消息被多個消費者消費

4.1消息生產者

    public void sendMessage(String exchange, String message, String routingKey) throws Exception {

        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明交換機(交換機沒有存儲的能力,在rabbitmq中只有隊列纔有存儲能力)
        // fanout:不處理路由鍵,會將消息轉發給所有綁定該交換機的隊列,因此將routingKey設置爲""
        // direct:處理路由鍵,將消息轉發給指定了routingKey的隊列上
        channel.exchangeDeclare(exchange, "fanout");
        // 發送消息
        channel.basicPublish(exchange, "", null, message.getBytes());
        // 關閉通道
        channel.close();
        connection.close();
    }

4.2消息消費者

    public void reciveByHandleMode(String exchange, String queueName, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 綁定到交換機
        channel.queueBind(queueName, exchange, "");
        // 可綁定多個
        // channel.queueBind(queueName, exchange, routingKey);
        // channel.queueBind(queueName, exchange, routingKey);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        // 定義消費者
        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("receiveMessage:" + message);
                // 程序確認,而不是rabbit的自動確認(手動回執)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列,改成false則表示手動應答
        boolean autoAck = false;
        channel.basicConsume(queueName, autoAck, consumer);
    }

5.路由

5.1消息生產者

    public void sendMessage(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明交換機(交換機沒有存儲的能力,在rabbitmq中只有隊列纔有存儲能力)
        // fanout:不處理路由鍵,會將消息轉發給所有綁定該交換機的隊列,因此將routingKey設置爲""
        // direct:處理路由鍵,將消息轉發給指定了routingKey的隊列上
        channel.exchangeDeclare(exchange, "direct");
        // 發送消息
        channel.basicPublish(exchange, routingKey, null, message.getBytes());
        // 關閉通道
        channel.close();
        connection.close();
    }

5.2消息消費者

    /**
     * 將隊列綁定到交換機
     *
     * @param exchange
     * @param queueName
     * @throws Exception
     */
    public void reciveByHandleMode(String exchange, String queueName, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 綁定到交換機
        channel.queueBind(queueName, exchange, routingKey);
        // 可綁定多個
        // channel.queueBind(queueName, exchange, routingKey);
        // channel.queueBind(queueName, exchange, routingKey);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        // 定義消費者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序確認,而不是rabbit的自動確認(手動回執)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列,改成false則表示手動應答
        channel.basicConsume(queueName, false, consumer);
    }

6.話題

 

6.1消息生產者

    public void sendMessage(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明交換機(交換機沒有存儲的能力,在rabbitmq中只有隊列纔有存儲能力)
        // fanout:不處理路由鍵,會將消息轉發給所有綁定該交換機的隊列,因此將routingKey設置爲""
        // direct:處理路由鍵,將消息轉發給指定了routingKey的隊列上
        //topic:將路由和某個模式進行匹配(規則匹配)。 #:匹配一個或者多個; *:匹配一個
        channel.exchangeDeclare(exchange, "topic");
        // 發送消息routingKey,such as :goods.add
        channel.basicPublish(exchange, routingKey, null, message.getBytes());
        // 關閉通道
        channel.close();
    }

6.2消息消費者

    /**
     * 將隊列綁定到交換機
     *
     * @param exchange
     * @param queueName
     * @throws Exception
     */
    public void reciveByHandleMode(String exchange, String queueName, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(queueName, false, false, false, null);
        // 綁定到交換機,發送消息routingKey,such as :goods.#
        channel.queueBind(queueName, exchange, routingKey);
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        // 定義消費者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("receiveMessage:" + message);
                // 程序確認,而不是rabbit的自動確認(手動回執)
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列,改成false則表示手動應答
        boolean autoAck = false;
        channel.basicConsume(queueName, autoAck, consumer);
    }

7.RPC

8.發佈者確認

生產者將消息發送至broker以後,生產者並不知道消息是否真的正確達到了目標隊列,rabbitMQ當然不會這麼傻,它提供了兩種方式,來告知生產者,消息是否正確投遞出去了。他們分別是:事務機制,確認機制。

8.1事務機制

投遞前開啓事務,投遞結束後提交事務,若發送異常,則回滾事務。

    public void createTransactionProducer(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明交換機(交換機沒有存儲的能力,在rabbitmq中只有隊列纔有存儲能力)
        // fanout:不處理路由鍵
        // direct:處理路由鍵
        channel.exchangeDeclare(exchange, "topic");
        try {
            // 發送消息routingKey,such as :goods.add
            channel.txSelect();
            channel.basicPublish(exchange, routingKey, null, message.getBytes());
            channel.txCommit();
        } catch (Exception e) {
            channel.txRollback();
        } finally {
            // 關閉通道
            channel.close();
        }
    }

8.2confirm機制

生產者將信道設置成confirm模式,一旦信道進入confirm模式,所有在該信道上發佈的消息都會被指派一個唯一的ID(默認從1開始),
一旦消息被投遞到所匹配的隊列之後,broker就會發送一個確認給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確的到達目標隊列了。
如果消息和隊列是可持久化的,那麼確認消息會在消息寫入磁盤之後發出,broker回傳給生產者的確認消息中deliver-tag域包含了確認消息的序列號。
此外,broker也可以設置basic.ack的multiple域,表示到這個序列號之前的所有消息都已經得到了處理。

8.2.1同步確認機制

    /**
     * @param exchange
     * @param message
     * @param routingKey
     * @throws Exception
     */
    public void createConfirmProducerSync(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明交換機(交換機沒有存儲的能力,在rabbitmq中只有隊列纔有存儲能力)
        // fanout:不處理路由鍵
        // direct:處理路由鍵
        channel.exchangeDeclare(exchange, "topic");
        try {
            // 發送消息routingKey,such as :goods.add
            channel.confirmSelect();
            channel.basicPublish(exchange, routingKey, null, message.getBytes());
            if (!channel.waitForConfirms()) {
                System.out.println("send message fail");
            } else {
                System.out.println("send message success");
            }
        } catch (Exception e) {

        } finally {
            // 關閉通道
            channel.close();
        }
    }

8.2.2異步確認機制

    public void createConfirmProducerAsync(String exchange, String message, String routingKey) throws Exception {
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道
        Channel channel = connection.createChannel();
        // 聲明交換機(交換機沒有存儲的能力,在rabbitmq中只有隊列纔有存儲能力)
        // fanout:不處理路由鍵
        // direct:處理路由鍵
        channel.exchangeDeclare(exchange, "topic");
        // 發送消息routingKey,such as :goods.add
        channel.confirmSelect();
        final SortedSet<Long> confirmSets = Collections.synchronizedSortedSet(new TreeSet<Long>());
        channel.addConfirmListener(new ConfirmListener() {

            // 沒有問題的handleAck
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    confirmSets.headSet(deliveryTag + 1).clear();
                } else {
                    confirmSets.remove(deliveryTag);
                }

            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    confirmSets.headSet(deliveryTag + 1).clear();
                } else {
                    confirmSets.remove(deliveryTag);
                }
            }

        });

        channel.basicPublish(exchange, routingKey, null, message.getBytes());
        // 關閉通道
        channel.close();
    }

 

 

 

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