RabbitMQ(四):訂閱模式

一、訂閱模式

官方內容參考:http://www.rabbitmq.com/tutorials/tutorial-three-java.html

即一個生產者發送消息給多個消費者,且每個消費者都收到一次,也即是一個消息能夠被多個消費者消費。

類似於我們訂閱同一微信公衆號,微信公衆號推送圖文,我們每個人都能收到一份。

image

二、fanout交換機

之前我們直接發送消息到隊列,這裏指定的交換機名稱是”“

channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

生產者發送消息給交換機,交換機分發綁定在交換機上的隊列,每個隊列對應一個消費者。

交換機有幾種類型,分別是:direct, topic, headers, fanout

這裏我們只討論一種,就是 fanout

fanout表示分發,即交換機的消息分發到每個綁定在交換機上的隊列

剩下的幾種之後討論。

根據上面的圖片所示,有幾個過程:

  1. 生產者發送消息給交換機
  2. 隊列綁定到交換機
  3. 消費者消費隊列

三、代碼演示

實際情況一般是隊列有可能對應多個消費者,比如一個註冊事件,既要發送郵件,又要發送短信,所以先把消息發送到交換機,然後分發到兩個隊列, 一個是郵件隊列,一個是短信隊列, 服務器可能有多臺,也即是每個隊列可能有多個消費者,爲了提高性能,也就是訂閱模式與工作隊列結合的一種形式。下面演示這個情況。

連接RabbitMQ工具類

package cn.saytime.rabbitmq.util;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * RabbitMQ連接工具類
 */
public class ConnectionUtil {

    private static final String host = "192.168.239.128";
    private static final int port = 5672;

    /**
     * 獲取RabbitMQ Connection連接
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);

//        connectionFactory.setUsername("test");
//        connectionFactory.setPassword("123456");
//        connectionFactory.setVirtualHost("/vhost_test");

        return connectionFactory.newConnection();
    }
}

如上所示,如果配置有用戶名密碼以及vhost,則配置即可。

生產者
package cn.saytime.rabbitmq.ps;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 生產者
 */
public class Send {

    private static final String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 獲取連接
        Connection connection = ConnectionUtil.getConnection();
        // 從連接開一個通道
        Channel channel = connection.createChannel();
        // 聲明一個fanout分發交換機
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        String message = "hello, ps";

        // 發送消息
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());

        System.out.println(" [x] Sent '" + message + "'");

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

    }
}

短信消費者

package cn.saytime.rabbitmq.ps;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 短信消費者
 */
public class Recv {

    // 短信隊列
    private static final String QUEUE_NAME = "test_queue_fanout_sms";
    private static final String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 獲取連接
        Connection connection = ConnectionUtil.getConnection();

        // 打開通道
        Channel channel = connection.createChannel();

        // 申明要消費的隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 綁定隊列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 這樣RabbitMQ就會使得每個Consumer在同一個時間點最多處理一個Message。換句話說,在接收到該Consumer的ack前,他它不會將新的Message分發給它。
        channel.basicQos(1);

        // 創建一個回調的消費者處理類
        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);
                System.out.println(" [1] Received '" + message + "'");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(" [1] done ");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 消費消息
        channel.basicConsume(QUEUE_NAME, false, consumer);

    }

}
郵件消費者
package cn.saytime.rabbitmq.ps;

import cn.saytime.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 郵件消費者
 */
public class Recv2 {

    // 郵件隊列
    private static final String QUEUE_NAME = "test_queue_fanout_email";
    private static final String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 獲取連接
        Connection connection = ConnectionUtil.getConnection();

        // 打開通道
        Channel channel = connection.createChannel();

        // 申明要消費的隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 綁定隊列到交換機
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 這樣RabbitMQ就會使得每個Consumer在同一個時間點最多處理一個Message。換句話說,在接收到該Consumer的ack前,他它不會將新的Message分發給它。
        channel.basicQos(1);

        // 創建一個回調的消費者處理類
        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);
                System.out.println(" [2] Received '" + message + "'");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(" [2] done ");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 消費消息
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }

}

四、測試結果

提前在管理控制檯創建一個direct交換機,或者先執行一次生產者(執行時會判斷交換機是否存在,不存在則創建交換機),這樣保證交換機存在,不然直接啓動消費者會提示交換機不存在。

這裏寫圖片描述

注意點

如果在沒有隊列綁定在交換機上面時,往交換機發送消息會丟失,之後綁定在交換機上面的隊列接收不到之前的消息,也就是先執行第一次發送,創建了交換機,但是還沒有隊列綁定在交換機上面,如果這次發送的消息就會丟失。

然後啓動兩個消費者,再執行生產者。

Send

 [x] Sent 'hello, ps'

Recv

 [1] Received 'hello, ps'
 [1] done 

Recv2

 [2] Received 'hello, ps'
 [2] done 

同樣的我們可以再加兩個消費者,分別消費短信和郵件,也就是變成了工作隊列。

所以上面代碼可以實現訂閱模式+工作隊列的結合。

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