RabbitMQ快速上手(二)發佈訂閱、路由、通配模式

在上篇文章中我們提到了RabbitMQ的幾種交換器模式,我們之前的HelloWord和Work模式都是採用的默認的Exchange即Directexchange ,接下來我們這章說一下剩下的幾種模式源碼

發佈/訂閱(fanout)

這裏寫圖片描述
發佈訂閱模式就像消費者訂閱(監聽)生產者一樣,只要生產者產生消息,消費者都可以消費

/**
 * 訂閱模式
 */
public class EmitLog {

    private static final String EXCHANGE_NAME = "logs";
    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        /*
         * 聲明exchange(交換機)
         * 參數1:交換機名稱
         * 參數2:交換機類型
         * 參數3:交換機持久性,如果爲true則服務器重啓時不會丟失
         * 參數4:交換機在不被使用時是否刪除
         * 參數5:交換機的其他屬性
         */
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        // 與前面不同, 生產者將消息發送給exchange, 而非隊列. 若發消息時還沒消費者綁定queue與該exchange, 消息將丟失

        for(int i=0;i<5;i++){
            String message = "Hello"+i;
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
            Thread.sleep(1000);
        }

        channel.close();
        connection.close();
    }
}
public class ReceiveLogs1 {

    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        String queueName = channel.queueDeclare().getQueue();
        /*
         * 綁定隊列到交換機(這個交換機的名稱一定要和上面的生產者交換機名稱相同)
         * 參數1:隊列的名稱
         * 參數2:交換機的名稱
         * 參數3:Routing Key
         */
        channel.queueBind(queueName, EXCHANGE_NAME, "");
        //注意:消息發送到沒有隊列綁定的交換機時,消息將丟失,因爲,交換機沒有存儲消息的能力,消息只能存在在隊列中。

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        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 + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }

}

另一個消費者代碼一樣 我就不貼了,接下來我們運行消費者程序
這裏寫圖片描述
消費者處在監聽狀態,接下里運行生產者生產消息
這裏寫圖片描述

現在可以看到兩個消費者都收到了一樣的消息
這裏寫圖片描述
這裏寫圖片描述

Routing(direct)

這裏寫圖片描述

我們採用路由的方式對不同的消息進行過濾,可以通過此種方式指定消息被哪些消費者消費
在生產者把消息傳遞給exchange時,需要說明消息的routing key,然後exchange根據消息的routing key匹配隊列。在說明隊列時也要說明隊列的routing key

/**
 * 路由模式
 */
public class RoutingSendDirect {

    private static final String EXCHANGE_NAME = "direct_logs";
    // 路由關鍵字
    private static final String[] routingKeys = new String[]{"info" ,"warning", "error"};
    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //聲明交換機
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");//注意是direct
        //發送信息
        for (String routingKey:routingKeys){
            String message = "RoutingSendDirect Send the message level:" + routingKey;
            channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
            System.out.println("RoutingSendDirect Send"+routingKey +"':'" + message);
        }
        channel.close();
        connection.close();
    }
}
public class ReceiveLogsDirect1 {
    // 交換器名稱
    private static final String EXCHANGE_NAME = "direct_logs";
    // 路由關鍵字
    private static final String[] routingKeys = new String[]{"info" ,"warning"};

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //聲明交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        //獲取匿名隊列名稱
        String queueName=channel.queueDeclare().getQueue();

        //根據路由關鍵字進行綁定
        for (String routingKey:routingKeys){
            channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
            //如果想讓消費者2同時接受routingKey爲A 和爲B的消息,只要在下面在此添加一個Bing就可以了
            System.out.println("ReceiveLogsDirect1 exchange:"+EXCHANGE_NAME+"," +
                    " queue:"+queueName+", BindRoutingKey:" + routingKey);
        }
        System.out.println("ReceiveLogsDirect1  Waiting for messages");
        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("ReceiveLogsDirect1 Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}
public class ReceiveLogsDirect2 {
    // 交換器名稱
    private static final String EXCHANGE_NAME = "direct_logs";
    // 路由關鍵字
    private static final String[] routingKeys = new String[]{"error"};

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        //聲明交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        //獲取匿名隊列名稱
        String queueName = channel.queueDeclare().getQueue();
        //根據路由關鍵字進行多重綁定
        for (String severity : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, severity);
            System.out.println("ReceiveLogsDirect2 exchange:"+EXCHANGE_NAME+", queue:"+queueName+", BindRoutingKey:" + severity);
        }
        System.out.println("ReceiveLogsDirect2 Waiting for messages");

        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("ReceiveLogsDirect2 Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

運行消費者程序
這裏寫圖片描述
這裏寫圖片描述
消費者分別監聽了routingKey 爲info,warning和error的消息
啓動生產者生產消息
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述

可以看到三種routingKey消息分別被路由到了不同消費者

Topics(topic)

這裏寫圖片描述
Topics模式和Routing相類似,都是通過Routing Key來做消息如隊列的匹配,RabbitMQ提供兩種匹配符:

  1. *(星號)匹配一個
  2. #(井號)匹配0個或多個
/**
 * 通配模式
 */
public class TopicSend {
    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] args) throws Exception {
        Connection connection = null;
        Channel channel = null;
        try{
            ConnectionFactory factory=new ConnectionFactory();
            factory.setHost("localhost");
            connection=factory.newConnection();
            channel=connection.createChannel();

            //聲明一個匹配模式的交換機
            channel.exchangeDeclare(EXCHANGE_NAME,"topic");
            //待發送的消息
            String[] routingKeys=new String[]{
                    "quick.orange.rabbit",
                    "lazy.orange.elephant",
                    "quick.orange.fox",
                    "lazy.brown.fox",
                    "quick.brown.fox",
                    "quick.orange.male.rabbit",
                    "lazy.orange.male.rabbit"
            };
            //發送消息
            for(String severity :routingKeys){
                String message = "From "+severity+" routingKey' s message!";
                channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
                System.out.println("TopicSend Sent '" + severity + "':'" + message + "'");
            }
        }catch (Exception e){
            e.printStackTrace();
            if (connection!=null){
                channel.close();
                connection.close();
            }
        }finally {
            if (connection!=null){
                channel.close();
                connection.close();
            }
        }
    }
}
public class ReceiveLogsTopic1 {
    private static final String EXCHANGE_NAME = "topic_logs";

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

        //聲明一個匹配模式的交換機
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = channel.queueDeclare().getQueue();
        //路由關鍵字
        String[] routingKeys = new String[]{"*.orange.*"};
        //綁定路由
        for (String routingKey : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
            System.out.println("ReceiveLogsTopic1 exchange:" + EXCHANGE_NAME + ", queue:" + queueName + ", BindRoutingKey:" + routingKey);
        }
        System.out.println("ReceiveLogsTopic1 Waiting for messages");

        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("ReceiveLogsTopic1 Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}
public class ReceiveLogsTopic2 {
    private static final String EXCHANGE_NAME = "topic_logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
//      聲明一個匹配模式的交換器
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String queueName = channel.queueDeclare().getQueue();
        // 路由關鍵字
        String[] routingKeys = new String[]{"*.*.rabbit", "lazy.#"};
//      綁定路由關鍵字
        for (String bindingKey : routingKeys) {
            channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
            System.out.println("ReceiveLogsTopic2 exchange:"+EXCHANGE_NAME+", queue:"+queueName+", BindRoutingKey:" + bindingKey);
        }

        System.out.println("ReceiveLogsTopic2 Waiting for messages");

        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("ReceiveLogsTopic2 Received '" + envelope.getRoutingKey() + "':'" + message + "'");
            }
        };
        channel.basicConsume(queueName, true, consumer);
    }
}

運行我們的消費者程序
這裏寫圖片描述
這裏寫圖片描述
消費者已經通過匹配的routingKey監聽,運行生產着生產消息
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述

可以看到消費者已經根據routingKey 消費到對應通配的消息

小結

我們上面的三種模式具有以下特點

  1. 生產者聲明瞭exchange 的類型,生產者種並沒有生命隊列,而是在消費者中由服務器隨機生成的
  2. 我們的隊列都是在消費者中聲明的,所以要先啓動消費者進行監聽

文章參考
https://javaduqing.github.io
http://www.cnblogs.com/LipeiNet/p/5978276.html

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