rabbitmq在exchange下的两种使用模式

上一篇,我们介绍了rabbimtmq的简单工作队列的使用方式,即生产者和消费者之间直接通过绑定相同的workqueue进行消息的发送和接收,如果业务逻辑比较简单,这样的方式也是可以用的,但在实际工作中,实际的业务场景远远比这个复杂,而且需要更加细粒度的对消息进行进行分发和接收,这就需要用到rabbitmq的另外一个组件exchange;

顾名思义,exchange在rabbitmq中翻译为交换机,交换机的作用可以理解为一个消息转发的中间容器,在这个容器里,为了满足消费端对消息的个性化需求,可以对原始的消息做一定程度的转换,类型过滤等操作,这样消费者接受到消息后处理起来相对更加便捷;

在rabbitmq中,exchange的使用主要对应着三种模式,fanout,topic,direct,即路由模式,发布订阅模式,下面的代码将模拟演示这两种主要的模式;

【1】发布订阅模式,pub/sub
假设有这样一个场景,气象局发布新闻,新浪,百度,QQ等第三方公司想要从气象局获取天气信息,就需要订阅气象局的天气消息,这里假如把气象局作为消息生产者,那么新浪,百度,QQ则当做消费者,

1)新建一个常量类,维护基本的队列信息和Exchange
public class RabbitConstant {

public static final String QUEUE_HELLOWORLD = "helloworld";
public static final String QUEUE_SMS = "sms";
public static final String EXCHANGE_WEATHER = "weather";
public static final String EXCHANGE_WEATHER_ROUTING = "weather_routing";
public static final String QUEUE_BAIDU = "baidu";
public static final String QUEUE_SINA = "sina";
public static final String EXCHANGE_WEATHER_TOPIC = "weather_topic";

}

2)创建通用的连接rabbitmq的服务器工具类,生产者和消费者都可以使用,

public class RabbitUtils {

private static ConnectionFactory factory = new ConnectionFactory();

static{
    factory.setHost("127.0.0.1");
    factory.setPort(5672);
    factory.setUsername("acong");
    factory.setPassword("acong");
    factory.setVirtualHost("/test");
}

public static Connection getConnection(){
    Connection connection = null;
    try {
        connection = factory.newConnection();
        return connection;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

}

此处需要注意的是,配置中的用户名,密码和vhost的信息需要提前在管控台下面配置好,并且提前将管控台跑起来,否则代码运行的时候报错,这里的配置信息我已经提前配置好了,

3)生产者代码:
public class WeatherBureau {

public static void main(String[] args) throws Exception{

    Connection connection = RabbitUtils.getConnection();
    String input = new Scanner(System.in).next();
    Channel channel = connection.createChannel();
    channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER,"" , null , input.getBytes());

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

}

这里为模拟真实的环境,一会儿将在开发工具的控制台下手动输入信息,看消费者接受消息的情况,

4)消费者端代码,
模拟百度接受消息:
public class Baidu {

public static void main(String[] args) throws Exception {

    Connection connection = RabbitUtils.getConnection();
    final Channel channel = connection.createChannel();
    channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
    //queueBind用于将队列与交换机绑定
    //参数1:队列名 参数2:交互机名  参数三:路由key(暂时用不到)
    channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER, "");
    channel.basicQos(1);
    System.out.println("百度准备接收信息.....");
    channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , new DefaultConsumer(channel){
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("百度收到气象信息:" + new String(body));
            channel.basicAck(envelope.getDeliveryTag() , false);
        }
    });
}

}

模拟新浪接受消息:
public class Sina {

public static void main(String[] args) throws Exception{

    Connection connection = RabbitUtils.getConnection();
    final Channel channel = connection.createChannel();
    //具体的消费者绑定到自定义的队列中
    channel.queueDeclare(RabbitConstant.QUEUE_SINA, false, false, false, null);
    //将队列绑定到指定的交换机
    channel.queueBind(RabbitConstant.QUEUE_SINA, RabbitConstant.EXCHANGE_WEATHER,"");
    channel.basicQos(1);
    System.out.println("新浪开始接收消息...");
    channel.basicConsume(RabbitConstant.QUEUE_SINA, false,new DefaultConsumer(channel){
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                throws IOException {
            System.out.println("新浪收到气象信息:" + new String(body));
            channel.basicAck(envelope.getDeliveryTag() , false);
        }
    });

}

}

从代码中可以看出来,生产者并没有具体绑定到某一个队列,而是声明了一个exchange,不同的消费者具有不同的queue,要接收到消息,只需要将各自的队列绑定到相同的exchange中即可,在消费者代码中,绑定交换机和队列的代码为:
channel.queueBind……

首先,分别启动两个消费者,然后在启动生产者,启动完毕后,在生产者的代码控制台中任意输入一条消息,可以看到,两个消费者都成功接收到了消息

【2】主题模式,topic模式,这种模式下,可以实现消费者对生产者发送的消息做一定的过滤和匹配;

仍然是上面的场景,但是需求发生了变化,假如百度在订阅的天气消息中只想要美国的消息,即以us字符串开头的消息,而新浪则只接受国内的以china的消息,在exchange下,可以对消息进行模糊匹配来实现这个需求,代码如下,

1)生产者代码,

public class WeatherBureau {

public static void main(String[] args) throws IOException, TimeoutException {

    Map area = new LinkedHashMap<String, String>();
    area.put("china.hebei.shijiazhuang.20991011", "中国河北石家庄20991011天气数据");
    area.put("china.shandong.qingdao.20991011", "中国山东青岛20991011天气数据");
    area.put("china.henan.zhengzhou.20991011", "中国河南郑州20991011天气数据");
    area.put("us.cal.la.20991011", "美国加州洛杉矶20991011天气数据");

    area.put("china.hebei.shijiazhuang.20991012", "中国河北石家庄20991012天气数据");
    area.put("china.shandong.qingdao.20991012", "中国山东青岛20991012天气数据");
    area.put("china.henan.zhengzhou.20991012", "中国河南郑州20991012天气数据");
    area.put("us.cal.la.20991012", "美国加州洛杉矶20991012天气数据");
    area.put("us.cal.la.20991012", "美国波士顿20991012天气数据");

    Connection connection = RabbitUtils.getConnection();
    Channel channel = connection.createChannel();
    Iterator<Map.Entry<String, String>> itr = area.entrySet().iterator();
    while (itr.hasNext()) {
        Map.Entry<String, String> me = itr.next();
        //Routing key 第二个参数相当于数据筛选的条件
        channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER_TOPIC,me.getKey() , null , me.getValue().getBytes());
    }

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

}

2)消费者代码,
模拟百度:
package com.acong.rabbitMq.topic;
import com.acong.rabbitMq.utils.RabbitConstant;
import com.acong.rabbitMq.utils.RabbitUtils;
import com.rabbitmq.client.*;

import java.io.IOException;

public class Baidu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
//queueBind用于将队列与交换机绑定
//参数1:队列名 参数2:交互机名 参数三:路由key
channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_TOPIC, “china.#”);
//channel.queueUnbind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_TOPIC, “..*.20991011”);
//.hebei..*
channel.basicQos(1);
channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(“百度收到气象信息:” + new String(body));
channel.basicAck(envelope.getDeliveryTag() , false);
}
});
}
}

模拟新浪:
public class Sina {
public static void main(String[] args) throws IOException {
Connection connection = RabbitUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(RabbitConstant.QUEUE_SINA, false, false, false, null);

    channel.queueBind(RabbitConstant.QUEUE_SINA,RabbitConstant.EXCHANGE_WEATHER_TOPIC, "us.#");

    channel.basicQos(1);
    channel.basicConsume(RabbitConstant.QUEUE_SINA ,false, new DefaultConsumer(channel){
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("新浪收到气象信息:" + new String(body));
            channel.basicAck(envelope.getDeliveryTag() , false);
        }
    });
}

}

分别启动两个消费者,再启动生产者,控制台可以看淡,两个消费者分别接收到了以us开头的和以china开头的消息;

当然,如果需要更精细的接收消息,比如新浪需要精确到具体的某个城市某一天的消息,则routingKey需要定义的更加精细一些;

以上为rabbitmq 在exchange下的发布订阅和路由的两种使用模式,实际业务中,可以据此参考,并在此基础做深入的拓展,以满足不同的业务需求。

猜你喜欢

转载自blog.csdn.net/zhangcongyi420/article/details/82590378