上一篇,我们介绍了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下的发布订阅和路由的两种使用模式,实际业务中,可以据此参考,并在此基础做深入的拓展,以满足不同的业务需求。