Rabbit之消息通信模式Simple、Work、Publish/Subscribe、Routing、Topics實踐

概述

說到消息通信,可能我們首先會想到的是郵箱,QQ,微信,短信等等這些通信方式,這些通信方式都有發送者,接收者,還有一箇中間存儲離線消息的容器。但是這些通信方式和我們要講的 RabbitMQ 的通信模型是不一樣的,比如和郵件的通信方式相比,郵件服務器基於 POP3/SMTP 協議,通信雙方需要明確指定,並且發送的郵件內容有固定的結構。

RabbitMQ 服務器基於 AMQP(全稱:Advanced Message Queuing Protocol 高級消息隊列協議) 協議,這個協議是不需要明確指定發送方和接收方的,而且發送的消息也沒有固定的結構,甚至可以直接存儲二進制數據,並且和郵件服務器一樣,也能存儲離線消息,最關鍵的是 RabbitMQ 既能夠以一對一的方式進行路由,還能夠以一對多的方式進行廣播

 

simple(無交換機)

點對點,一對一,一個生產者,一個消費者,直接和隊列交互

 

work(無交換機)

也是點對點,,一個生產者,兩個(多個)消費者,直接和隊列交互

多個消費者,默認是輪詢消費

弊端:引起處理快的消費者處於等待狀態,資源浪費。處理慢的消費者消息堆積

解決:限流+手工回覆模式

模擬兩個消費者處理消息速度不一樣:設置其中的一個消費者一次只接收處理一條消息,處理完才繼續接收,手動回覆消息確認

生產者(發送一萬條消息)

public class Send {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    public static final String QUEUE_NAME = "WORK_QUEUE";


    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //隊列聲明,隊列不存在則創建,否則不做處理
        //exclusive:獨有的,是否只允許當前連接使用此隊列
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        for (int i = 1; i <= 10000; i++) {
            String msg="第" + i+ "條隊列消息";
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
        }
        System.out.println("發送完成");
    }
}

消費者1

public class Receiving {
    public static final String QUEUE_NAME = "WORK_QUEUE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
       //創建消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //等待隊列有消息之後,自動回調
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //super.handleDelivery(consumerTag, envelope, properties, body);
                //消費消息
                String msg = new String(body,"utf-8");
                System.out.println("消費者1處理消息:\t" + msg);
            }
        };
        //讓消費者監聽隊列
        //消費後ACK確認消息成功消費
        channel.basicConsume(QUEUE_NAME,true,consumer);
        System.out.println("成功消費");
    }
}

 

消費者2,設置限流

ps:

//設置一次只接收處理一條消息,別一次把所有消息都給到這裏
//處理完纔會繼續處理下一條消息
 channel.basicQos(1);

//手工回覆確認消息處理完畢

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

//設置不自動ack確認

 channel.basicConsume(QUEUE_NAME,false,consumer);

代碼如下

public class Receiving2 {
    public static final String QUEUE_NAME = "WORK_QUEUE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //設置一次只接收處理一條消息,別一次把所有消息都給到這裏
        //處理完纔會繼續處理下一條消息
        channel.basicQos(1);
        //創建消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //等待隊列有消息之後,自動回調
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //super.handleDelivery(consumerTag, envelope, properties, body);
                //消費消息
                String msg = new String(body,"utf-8");
                System.out.println("消費者2處理消息:\t" + msg);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channel.basicAck(envelope.getDeliveryTag(),false);//手工回覆確認消息處理完畢
            }
        };
        //讓消費者監聽隊列
        //消費後ACK確認消息成功消費
        channel.basicConsume(QUEUE_NAME,false,consumer);
        System.out.println("成功消費");
    }
}

消費者1處理了幾乎全部的消息

消費者2,一萬條消息中處理了10條左右,即限流成功

 

 

Publish/Subscribe(交換機,type=fanout)

圖片解析:

  1. 生產者聲明交換機。
  2. 消費者聲明隊列,並和交換機綁定。

簡單的發佈訂閱模式和上面的點對點方式有區別

區別描述:

  • 發佈訂閱模式中,生產者只和交換機打交道,聲明交換機,然後將消息發送給交換機就完事了。
  • 而消費者這邊,交換機需要先和隊列綁定,然後消費者從隊列中取出消息。
  • 所以消費者中要先聲明交換機和隊列,然後交換機和隊列綁定,消費者才能消費消息

ps:第一次消息丟失

發佈訂閱模式,如果生產者先啓動,生產者第一次發送消息到交換機中,因爲交換機還沒有和任何隊列綁定關係,所以交換機不會將消息轉到對應的隊列中存儲,導致了第一次發送的消息丟失。

解決方案:可以在rabbitmq管理界面事先建立好交換機和隊列,然後進行綁定。

進入管理頁面:

創建交換機

創建兩個隊列

交換機和隊列綁定

點擊創建好的交換機

交換機分別綁定隊列1、2

 

完成綁定,這樣即使是第一次啓動,生產者發送的消息也不會丟失了

 

生產者(聲明交換機)

public class Send {
    public static final String EXCHANGE_NAME = "FANOUT_EXCHANGE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //交換機聲明,交換機不存在則創建,否則不做處理
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout",true);
        for (int i = 1; i <= 10; i++) {
            String msg="第" + i+ "條隊列消息";
            channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
        }
        System.out.println("發送完成");
    }
}

消費者1(聲明隊列並綁定交換機)

public class Receiving {
    public static final String QUEUE_NAME = "FANOUT_EXCHANGE_QUEUE1";
    public static final String EXCHANGE_NAME = "FANOUT_EXCHANGE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //交換機和隊列綁定
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
       //創建消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //等待隊列有消息之後,自動回調
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //super.handleDelivery(consumerTag, envelope, properties, body);
                //消費消息
                String msg = new String(body,"utf-8");
                System.out.println("消費者1處理消息:\t" + msg);
            }
        };
        //讓消費者監聽隊列
        //消費後ACK確認消息成功消費
        channel.basicConsume(QUEUE_NAME,true,consumer);
        System.out.println("成功消費");
    }
}

 

消費者2(聲明隊列並綁定交換機)

public class Receiving2 {
    public static final String QUEUE_NAME = "FANOUT_EXCHANGE_QUEUE2";
    public static final String EXCHANGE_NAME = "FANOUT_EXCHANGE";


    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //交換機和隊列綁定
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        //設置一次只接收處理一條消息,別一次把所有消息都給到這裏
        //處理完纔會繼續處理下一條消息
        channel.basicQos(1);
        //創建消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //等待隊列有消息之後,自動回調
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //super.handleDelivery(consumerTag, envelope, properties, body);
                //消費消息
                String msg = new String(body,"utf-8");
                System.out.println("消費者2處理消息:\t" + msg);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channel.basicAck(envelope.getDeliveryTag(),false);//手工回覆確認消息處理完畢
            }
        };
        //讓消費者監聽隊列
        //消費後ACK確認消息成功消費
        channel.basicConsume(QUEUE_NAME,false,consumer);
        System.out.println("成功消費");
    }
}

結果:生產者發送了10條消息,消費者1、2都拿到了10條消息

即:生產者發送多少消息,消費者1、2就拿到消息

 

Routing(交換機,type=direct

即:生產者發佈消息到交換機,消費者按照不同的需求,訂閱不同類型的消息。

交換機按照隊列訂閱的消息類型轉發不同的消息到隊列中

和上面的發佈訂閱一樣,只是需要設置routingKey

  1. 生產者在發佈消息的時候設置好消息的routingKey類型
  2. 消費者在和交換機綁定的時候,設置需要訂閱的消息routingKey類型

 

生產者(發送三種不同類型RoutingKey的消息)

public class Send {
    public static final String EXCHANGE_NAME = "DIRECT_EXCHANGE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //交換機聲明,交換機不存在則創建,否則不做處理
        channel.exchangeDeclare(EXCHANGE_NAME,"direct",true);
        channel.basicPublish(EXCHANGE_NAME,"nba",null,"渣渣怎麼可能進得去NBA".getBytes());
        channel.basicPublish(EXCHANGE_NAME,"cba",null,"渣渣怎麼可能進得去CBA".getBytes());
        channel.basicPublish(EXCHANGE_NAME,"ncba",null,"渣渣怎麼可能進得去NCBA".getBytes());
        System.out.println("發送完成");
    }
}

消費者1(設置訂閱的routingKey類型消息)

public class Receiving {
    public static final String QUEUE_NAME = "DIRECT_EXCHANGE_QUEUE1";
    public static final String EXCHANGE_NAME = "DIRECT_EXCHANGE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //交換機和隊列綁定
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"nba");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"cba");
       //創建消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //等待隊列有消息之後,自動回調
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //super.handleDelivery(consumerTag, envelope, properties, body);
                //消費消息
                String msg = new String(body,"utf-8");
                System.out.println("消費者1處理消息:\t" + msg);
            }
        };
        //讓消費者監聽隊列
        //消費後ACK確認消息成功消費
        channel.basicConsume(QUEUE_NAME,true,consumer);
        System.out.println("成功消費");
    }
}

消費者2(設置訂閱的routingKey類型消息)

public class Receiving2 {
    public static final String QUEUE_NAME = "DIRECT_EXCHANGE_QUEUE2";
    public static final String EXCHANGE_NAME = "DIRECT_EXCHANGE";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.239.129");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("mocar");
        connectionFactory.setPassword("mocar");
        connectionFactory.setVirtualHost("/mocar");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //聲明隊列
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //交換機和隊列綁定
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"nba");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"ncba");
        //設置一次只接收處理一條消息,別一次把所有消息都給到這裏
        //處理完纔會繼續處理下一條消息
        channel.basicQos(1);
        //創建消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            //等待隊列有消息之後,自動回調
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //super.handleDelivery(consumerTag, envelope, properties, body);
                //消費消息
                String msg = new String(body,"utf-8");
                System.out.println("消費者2處理消息:\t" + msg);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channel.basicAck(envelope.getDeliveryTag(),false);//手工回覆確認消息處理完畢
            }
        };
        //讓消費者監聽隊列
        //消費後ACK確認消息成功消費
        channel.basicConsume(QUEUE_NAME,false,consumer);
        System.out.println("成功消費");
    }
}

結果:

 

Topics(交換機,type=topic

即:按通配符的方式發佈和訂閱消息

 

 

 

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