一、模型
- P(Producer):生产者——发送消息
- X(Exchange):交换机——一方面接受生产者发送的消息,另一方面是向队列中推送消息
- Q(Queue): 消息队列(图中红色方形)——存储消息
- C(consumer): 消费者——接收消息
- Binding:绑定——交换机与队列的绑定关系
- Routing Key:路由键——消息和绑定都有此属性,交换机推送消息时,会在与之绑定的队列中寻找与消息本身路由键一致的队列进行推送。
路由模式( Routing):发送的消息以及与交换机与队列绑定的关系都需设置自属的路由键,生产者向交换机发送消息之后,交换机会在与之绑定的队列中寻找路由键与消息的路由键保持一致的队列推送该消息。若没有相匹配的,则该消息丢失。
模型解读
- 交换机与队列之间的绑定都设定了自属的路由键;
- 消息自己本身也需要设置路由键;
- 交换机推送消息时需验证消息的路由键与绑定的路由键保持完全一致才会推送。
注:路由模式( Routing)需将交换机(Exchange)类型定义为"Direct"。
二、Direct Exchange(直连交换机)
1、模型
2、含义
直连交换机(Direct Exchange)会根据消息自身所携带的路由键(Routing Key)在所有的绑定关系中寻找,与消息的路由键保持完全一致的队列推送该消息。
3、与扇形交换机(Fanout Exchange)的不同
3.1、扇形交换机中,消息和绑定关系都是不需要设置路由键(Routing Key)的,但是直连交换机中却必须要设定;
3.2、扇形交换机不受路由键的约束,所以会将一条消息推送至所有与之绑定的队列中去,但是直连交换机推送消息的前提是,消息携带的路由键必须完全与绑定关系的路由键一致,才会推送。
三、Java编程实现
1、导入AMQP协议jar包,以及创建RabbitMQ连接工具类,请查看三、RabbitMQ之简单队列(Simple Queue);
2、创建生产者发送消息;
package com.rabbitMQ.routing;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 路由模式-生产者
* @author zhoujin
* @data 2019-1-22
*/
public class RoutingProducer {
private static final String EXCHANGE_NAME = "exchange_direct";
private static final String ROUTING_ERROR = "error";
private static final String ERROR_MESSAGE = "This is direct MQ, routing key is error!";
private static final String ROUTING_INFO = "info";
private static final String INFO_MESSAGE = "This is direct MQ, routing key is info!";
public static void main(String[] args) {
Connection conn = null;
Channel channel = null;
try {
// 1.获取连接
conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
channel = conn.createChannel();
// 3.声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
// 4.发送消息
String message = ERROR_MESSAGE;
channel.basicPublish(EXCHANGE_NAME, ROUTING_ERROR, null, message.getBytes());
System.out.println("======================= Direct MQ send message end! 【Content:" + message + "】 =======================");
message = INFO_MESSAGE;
channel.basicPublish(EXCHANGE_NAME, ROUTING_INFO, null, message.getBytes());
System.out.println("======================= Direct MQ send message end! 【Content:" + message + "】 =======================");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
try {
ConnectionUtils.closeConnection(channel, conn);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
3、创建消费者1接收消息;
package com.rabbitMQ.routing;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 路由模式-第一个生产者(error)
* @author zhoujin
* @data 2019-1-22
*/
public class RoutingFirstConsumer {
private static final String QUEUE_NAME = "queue_direct_first";
private static final String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) {
try {
// 1.获取连接
Connection conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
final Channel channel = conn.createChannel();
// 3.声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.将队列绑定到交换机上
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
// 5.保证一次只接收一条消息
channel.basicQos(1);
// 6.创建消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("======================= First direct(error) consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 7.手动发送反馈回执
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 8.监听队列(关闭自动回复,即第二个参数设置为false)
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
4、创建消费者2接收消息。
package com.rabbitMQ.routing;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitMQ.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 路由模式-第二个消费者(error/info)
* @author zhoujin
* @data 2019-1-22
*/
public class RoutingSecondConsumer {
private static final String QUEUE_NAME = "queue_direct_second";
private static final String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) {
try {
// 1.获取连接
Connection conn = ConnectionUtils.getConnection();
// 2.从连接中获取通道
final Channel channel = conn.createChannel();
// 3.声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 4.将队列绑定到交换机上
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "info");
// 5.保证一次只接收一条消息
channel.basicQos(1);
// 6.创建生产者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope, BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println("======================= Second direct(error/info) consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 7.手动发送反馈回执
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 8.监听队列(关闭自动回复,即第二个参数设置为false)
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
注: 若只在生产者代码中定义了交换机,而未在消费者中定义队列或定义了队列但未与交换机绑定,程序编译和运行都不会报错,但是消息会被丢失。因为交换机没有存储消息的能力,RabbitMQ中只有队列有存储消息的能力。
四、运行代码以及控制台输出
1、 运行生产者代码——定义交换机;
① 为何这里需要先运行生产者的代码?
因为需要先在生产者中定义交换机。若先启动消费者,则会报错提示"no exchange"错误,原因是未定义交换机!
② 为何此次生产者发送的消息丢失了?
因为此时还没有队列与之绑定,而交换机本身是不具备存储消息的功能,因此会导致消息丢失。
2、运行两个消费者的代码;
2.1、在Web管理页面查看交换机与队列的绑定关系;
3、再次运行生产者代码——发送消息。
3.1、生产者控制台输出;
3.2、第一个消费者(error)控制台输出;
3.3、第二个消费者(error&info)控制台输出