【RabbitMQ】——佇列模式(2)

楊小嘿發表於2017-07-30
        本篇部落格接著上一篇介紹MQ的佇列模式。
訂閱模式
 
        P:生產者
        c1、c2:消費者
        紅色:訊息佇列
        x:交換機


        這種模式猛地一看和Work模式很像,但是這個模式中的每個消費者都有自己的佇列,同時引入了一個新的概念——交換機。
        互動機分別繫結生產者和訊息佇列,生產者傳送的訊息會經過交換機到達佇列,這樣可以實現一個訊息被多個消費者獲取的目的。


注意
        如果交換機沒有繫結佇列,生產者傳送的訊息會丟失,因為互動機沒有儲存訊息的能力,訊息只能存在佇列之中。



生產者程式碼:
public class Send {
    private final static String EXCHANGE_NAME = "test_exchange_fanout";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 宣告交換機(exchange)
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");   
        String message = "Hello World"; // 訊息內容
        // 將訊息傳送到交換機
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        channel.close();
        connection.close();
    }
}


消費者一程式碼:
public class Recv {

    private final static String QUEUE_NAME = "test_queue_fanout_1";
    private final static String EXCHANGE_NAME = "test_exchange_fanout";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 宣告佇列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 繫結佇列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        // 同一時刻伺服器只會發一條訊息給消費者
        channel.basicQos(1);
        // 定義佇列的消費者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 監聽佇列,手動返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
        // 獲取訊息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" 前臺系統: '" + message + "'");
            Thread.sleep(10);
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}


消費者二程式碼:
public class Recv2 {

    private final static String QUEUE_NAME = "test_queue_fanout_2";
    private final static String EXCHANGE_NAME = "test_exchange_fanout";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 宣告佇列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 繫結佇列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");


        // 同一時刻伺服器只會發一條訊息給消費者
        channel.basicQos(1);
        // 定義佇列的消費者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 監聽佇列,手動返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
        // 獲取訊息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" 搜尋系統: '" + message + "'");
            Thread.sleep(10);
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

        以上消費者一和消費者二種分別宣告瞭自己的佇列,都繫結到了同一個在生成者端宣告的交換機中。
        生產者傳送一條訊息到交換機,繫結到了交換機的佇列都可以獲得這條訊息,實現一條訊息被多個消費者獲得的功能。


        在RabbitMQ的管理工具中可以檢視繫結到交換機的佇列,如下:
 

路由模式
 


        路由模式中的元素和訂閱者模式中的元素一樣,但是路由模式是在訂閱者模式基礎上的升級。訂閱者模式中的佇列繫結了交換機後會接受生成者傳送的所有訊息,但是消費者可能並不想接受所有的訊息,路由模式可以實現訊息的訂製。
        路由模式在生產者傳送訊息時會宣告一個key,消費者的佇列在繫結交換機是會表明自己接受什麼key值的訊息,下面來看一下程式碼:


生產者程式碼:

public class Send {
    private final static String EXCHANGE_NAME = "test_exchange_direct";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 宣告exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        // 訊息內容
        String message1 = "刪除資訊, id = 1001";
        String message2 = "更新資訊, id = 1002";
        // 傳送訊息,宣告訊息的key
        channel.basicPublish(EXCHANGE_NAME, "delete", null, message1.getBytes());
        channel.basicPublish(EXCHANGE_NAME, "update", null, message2.getBytes());
        System.out.println(" [x] Sent '" + message1 + "'");
        System.out.println(" [x] Sent '" + message2 + "'");
        channel.close();
        connection.close();
    }
}

消費者程式碼:
public class Recv {
    private final static String QUEUE_NAME = "test_queue_direct_1";
    private final static String EXCHANGE_NAME = "test_exchange_direct";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();


        // 宣告佇列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 繫結佇列到交換機,宣告接受訊息的key 
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "delete");
        // 同一時刻伺服器只會發一條訊息給消費者
        channel.basicQos(1);
        // 定義佇列的消費者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 監聽佇列,手動返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
        // 獲取訊息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" 前臺系統: '" + message + "'");
            Thread.sleep(10);
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

        生產者傳送了兩條訊息,一條key為delete,另一條key為update。在消費者端,佇列繫結互動機時宣告瞭delete這個key,這樣當前消費者只會接受到delete訊息,不會接收到奧update訊息。


萬用字元模式
 


        萬用字元模式中的元素和訂閱者模式及路由模式中的元素一樣,而萬用字元模式是在路由模式基礎上的升級。路由模式需要消費者端的佇列在繫結交換機時宣告完整的key,如果需要接受多個key的訊息則需要繫結多條。萬用字元模式就類似模糊匹配,只需要宣告部分關鍵字即可。


萬用字元有兩種:# 和 *
        #:可以匹配一個或多個詞
        *:只能匹配一個詞
例:
        item.category.add,使用#寫為item.#即可匹配到,使用*則需寫為item.*.*


生產者程式碼:
public class Send {
    private final static String EXCHANGE_NAME = "test_exchange_topic";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 宣告exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        // 訊息內容
        String message1 = "刪除,id = 1001";
        String message2 = "更新,id = 1002";
        channel.basicPublish(EXCHANGE_NAME, "item.delete", null, message1.getBytes());
        channel.basicPublish(EXCHANGE_NAME, "item.update", null, message2.getBytes());
        System.out.println(" [x] Sent '" + message1 + "'");
        System.out.println(" [x] Sent '" + message2 + "'");
        channel.close();
        connection.close();
    }
}

消費者程式碼:
public class Recv2 {
    private final static String QUEUE_NAME = "test_queue_topic_2";
    private final static String EXCHANGE_NAME = "test_exchange_topic";
    public static void main(String[] argv) throws Exception {
        // 獲取到連線以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 宣告佇列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 繫結佇列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "item.#");
        // 同一時刻伺服器只會發一條訊息給消費者
        channel.basicQos(1);
        // 定義佇列的消費者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 監聽佇列,手動返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);
        // 獲取訊息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" 搜尋系統: '" + message + "'");
            Thread.sleep(10);
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}

        消費者端的的佇列在繫結交換機時只需要寫item.#即可匹配到item.delete和item.update。

小結

        本篇文章中的三種訊息佇列包含的元素相同,功能是越來越靈活的,之所以能實現這種效果,是因為交換機有不同的模式,三種模式的生產者端在宣告交換機時分別使用了不同的模式。

        訂閱模式——Fanout

        路由模式——Direct
        通配模式——Topic

相關文章