1、概述
Routing是路由模式,它有如下特点:
1、每个消费者监听自己的队列,并且设置routingkey(路由key)。
2、生产者将消息发给交换机,交换机根据routingkey来转发消息到指定的队列。
该图中,指明了Routing模式的工作原理,X表示的交换机,交换机类型要设置为direct,error,info等都是routingKey。
2、代码实现
2.1 生产者代码
注意:声明exchange_routing_inform交换机;声明两个队列并且绑定到此交换机,绑定时需要指定routingkey;发送消息时需要指定routingkey 。
package com.zdw.prodcure;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Create By zdw on 2019/9/17
* 这是Routing模式的生成者
*/
public class RoutingProdcure {
private static final String EXCHANGE_DIRECT_NAME="exchange_direct_inform";//定义交换机名称
private static final String QUEUE_MSG="queue_msg_inform";//短信队列名称
private static final String QUEUE_EMAIL="queue_email_inform";//邮件队列名称
private static final String ROUTING_MSG="routing_msg";//短信队列的routingKey
private static final String ROUTING_EMAIL="routing_email";//邮件队列的routingKey
private static final String ROUTING_COMMON="routing_common";//短信队列和邮件队列共同的routingKey
public static void main(String[] args) {
Connection connection = null;//定义连接对象
Channel channel = null;//定义通道对象
try {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务
connection = connectionFactory.newConnection();//得到与RabbitMQ服务的TCP连接
channel = connection.createChannel();//从连接中得到与交换机的通道,一个连接可以创建多个通道,一个通道代表一个会话任务
/**
* 声明两个队列,String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* 参数明细:
* 1、String queue:队列名称
* 2、boolean durable:是否持久化,如果持久化,mq重启后队列还在
* 3、exclusive :是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
* 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
* 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
*/
channel.queueDeclare(QUEUE_MSG,true,false,false,null);
channel.queueDeclare(QUEUE_EMAIL,true,false,false,null);
/**
* 声明交换机:
* 参数:String exchange, BuiltinExchangeType type
* 1、exchange:交换机名称
* 2、type:交换机类型,主要有以下几种类型:
* fanout:对应的是发布订阅模式
* direct:对应的是routing模式
* topic:对应的是ropic模式
* headers 对应的是headers工作模式
*/
channel.exchangeDeclare(EXCHANGE_DIRECT_NAME, BuiltinExchangeType.DIRECT);
/**
* 队列绑定到交换机,并且指定路由key
* 参数:String queue, String exchange, String routingKey
* 1、队列名称 2、交换机名称 3、路由key,作用是交换机根据路由key的值将消息转发到指定的队列中
*/
channel.queueBind(QUEUE_MSG,EXCHANGE_DIRECT_NAME,ROUTING_MSG);
channel.queueBind(QUEUE_EMAIL,EXCHANGE_DIRECT_NAME,ROUTING_EMAIL);
//绑定到队列,指定路由为共同的路由key
channel.queueBind(QUEUE_MSG,EXCHANGE_DIRECT_NAME,ROUTING_COMMON);
channel.queueBind(QUEUE_EMAIL,EXCHANGE_DIRECT_NAME,ROUTING_COMMON);
/**
* 发送消息:String exchange, String routingKey, BasicProperties props, byte[] body
* 参数明细:
* 1、exchange 交换机 如果不指定将使用mq的默认交换机(设置为"")
* 2、routingKey 路由key 交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称
* 3、props 消息的属性
* 4、body 消息主体
* 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
* 默认的交换机,routingKey等于队列名称
*/
for(int i=0;i<5;i++) {
//定义发送的消息内容
String message = "send inform msg to user:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS").format(new Date());
//发送消息到短信队列,这里指定了路由key是ROUTING_MSG,所以只有短信队列可以接收该消息
channel.basicPublish(EXCHANGE_DIRECT_NAME, ROUTING_MSG, null, message.getBytes());
System.out.println("Send message to msg MQ:" + message);
}
for(int i=0;i<4;i++) {
//定义发送的消息内容
String message = "send inform email to user:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS").format(new Date());
//发送消息到短信队列,这里指定了路由key是ROUTING_EMAIL,所以只有邮件队列可以接收该消息
channel.basicPublish(EXCHANGE_DIRECT_NAME, ROUTING_EMAIL, null, message.getBytes());
System.out.println("Send message to email MQ:" + message);
}
//发送消息时指定共同的路由key,那么两个队列都能接收到消息
for(int i=0;i<3;i++) {
//定义发送的消息内容
String message = "send inform email and msg to user:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS").format(new Date());
//发送消息到短信队列,这里指定了路由key是ROUTING_COMMON,所以短信和邮件队列都可以接收该消息
channel.basicPublish(EXCHANGE_DIRECT_NAME, ROUTING_COMMON, null, message.getBytes());
System.out.println("Send message to email and msg MQ:" + message);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(channel!=null){
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
生产者代码中,声明了一个交换机,两个队列,三个路由key,两个队列都绑定到了交换机并且指定了各自特定的路由key和共同的路由key。发送消息的时候,第一个循环中,发送消息的时候指定了路由key是ROUTING_MSG,因为短信队列QUEUE_MSG绑定到交换机的时候指定了路由key是ROUTING_MSG,所以消息只会发送到QUEUE_MSG队列中;第二个循环中,发送的消息的时候指定了路由key是ROUTING_EMAIL,因为邮件队列QUEUE_EMAIL绑定到交换机的时候指定了路由ROUTING_EMAIL,所以消息只会发送到QUEUE_EMAIL队列中;第三个循环中,发送消息的时候指定了路由key是ROUTING_COMMON,因为短信队列QUEUE_MSG和邮件队列QUEUE_EMAIL都绑定到交换机的时候指定了路由ROUTING_COMMON,所以消息会发送到两个队列中。
2.2 消费者代码
2.2.1 短信消费者
package com.zdw.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* Create By zdw on 2019/9/18
* 短信消费者
*/
public class RoutingConsumerMsg {
private static final String EXCHANGE_NAME="exchange_direct_inform";//定义交换机名称,一定要与生产者中相同
private static final String QUEUE_MSG="queue_msg_inform";//短信队列名称,一定要与生产者中相同
private static final String ROUTING_MSG="routing_msg";//短信队列的routingKey
private static final String ROUTING_COMMON="routing_common";//短信队列和邮件队列共同的routingKey
public static void main(String[] args) {
Connection connection = null;//定义连接对象
Channel channel = null;//定义通道对象
try {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务
connection = connectionFactory.newConnection();//获取与RabbitMQ的tcp连接
channel = connection.createChannel();//得到连接通道,一个连接可以创建多个通道,每个通道相当于一个会话任务
channel.queueDeclare(QUEUE_MSG,true,false,false,null);//声明队列
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//声明交换机
channel.queueBind(QUEUE_MSG,EXCHANGE_NAME,ROUTING_MSG);//绑定队列到交换机,指定路由key
channel.queueBind(QUEUE_MSG,EXCHANGE_NAME,ROUTING_COMMON);//绑定队列到交换机,指定路由key
//定义消费消息的方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 消费者接收消息后执行该方法
* @param consumerTag 消费者标签,用来标识消费者的,在监听队列时(channel.basicConsume)设置
* @param envelope 信封,通过envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,
* 消息和重传标志(收到消息失败后是否需要重新发送)
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String exchange = envelope.getExchange();
String routingKey = envelope.getRoutingKey();
long msgId = envelope.getDeliveryTag();//消息id
String msg = new String(body,"utf-8");
System.out.println(exchange+"//"+routingKey+"//"+msgId+"//"+msg);
}
};
//监听队列
/** 参数:String queue, boolean autoAck, Consumer callback
* 1、queue 队列名称
* 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
* 3、callback,消费方法,当消费者接收到消息要执行的方法
*/
channel.basicConsume(QUEUE_MSG,true,defaultConsumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
2.2.2 邮件消费者
package com.zdw.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* Create By zdw on 2019/9/18
* 邮件消费者
*/
public class RoutingConsumerEmail {
private static final String EXCHANGE_NAME="exchange_direct_inform";//定义交换机名称,一定要与生产者中相同
private static final String QUEUE_EMAIL="queue_email_inform";//邮件队列名称,一定要与生产者中相同
private static final String ROUTING_EMAIL="routing_email";//邮件队列的routingKey
private static final String ROUTING_COMMON="routing_common";//短信队列和邮件队列共同的routingKey
public static void main(String[] args) {
Connection connection = null;//定义连接对象
Channel channel = null;//定义通道对象
try {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务
connection = connectionFactory.newConnection();//获取与RabbitMQ的tcp连接
channel = connection.createChannel();//得到连接通道,一个连接可以创建多个通道,每个通道相当于一个会话任务
channel.queueDeclare(QUEUE_EMAIL,true,false,false,null);//声明队列
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//声明交换机
channel.queueBind(QUEUE_EMAIL,EXCHANGE_NAME,ROUTING_EMAIL);//绑定队列到交换机,指定路由key
channel.queueBind(QUEUE_EMAIL,EXCHANGE_NAME,ROUTING_COMMON);//绑定队列到交换机,指定路由key
//定义消费消息的方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 消费者接收消息后执行该方法
* @param consumerTag 消费者标签,用来标识消费者的,在监听队列时(channel.basicConsume)设置
* @param envelope 信封,通过envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,
* 消息和重传标志(收到消息失败后是否需要重新发送)
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String exchange = envelope.getExchange();
String routingKey = envelope.getRoutingKey();
long msgId = envelope.getDeliveryTag();//消息id
String msg = new String(body,"utf-8");
System.out.println(exchange+"//"+routingKey+"//"+msgId+"//"+msg);
}
};
//监听队列
/** 参数:String queue, boolean autoAck, Consumer callback
* 1、queue 队列名称
* 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
* 3、callback,消费方法,当消费者接收到消息要执行的方法
*/
channel.basicConsume(QUEUE_EMAIL,true,defaultConsumer);
}catch (Exception e){
e.printStackTrace();
}
}
}
使用生产者发送若干条消息,交换机根据routingkey转发消息到指定的队列。
我们发现生产者只把消息转发到指定路由key的队列中,消费者也只能消费指定路由key的消息
注意:上面的生产者和消费者都声明了队列,声明了交换机,进行了队列绑定到交换机的操作。这样做是为了无论先启动的生产者还是先启动消费者,都不会报错。其实,只要有一方进行声明就行了的。
代码下载:https://download.csdn.net/download/zengdongwen/11784368