目录
阅读本篇文章之前建议先阅读前三个模式:RabbitMQ学习(二):工作模式——简单队列,工作队列,发布-订阅模式。能够更好的承上启下。消息确认机制:RabbitMQ学习(四):消息确认机制——事务模式,Confirm模式。
1、路由模式
路由模式和发布订阅模式一样,都是通过交换机实现一条消息被多个消费者接收。不同的是,路由模式能够做到控制消息只被某一些满足条件的消费者所接收。
是怎么实现的呢?就是通过路由键(routingKey)来实现的,生产者发送消息会携带一个KEY,只有拥有这个KEY的消费者才会接收到对应消息。
生产者需要在发布订阅模式的基础上:
- 修改交换机类型为direct
- 设置每条消息的路由键
public class Producer {
private static final String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
// 从连接中获取通道
Channel channel = connection.createChannel();
// 创建交换机,direct:处理路由键
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String msg = "hello error";
// 指定路由键,并携带到消息中
String routingKey = "error";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
System.out.println("send-->" + msg);
// 第二条消息,更换路由键
msg = "hello warning";
routingKey = "warning";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
System.out.println("send-->" + msg);
channel.close();
connection.close();
}
}
消费者需要指定自己能接收哪些路由键的消息。
/**
* 消费者2(error)
*/
public class Consumer1 {
private static final String QUEUE_NAME = "queue_exchange_direct_email";
private static final String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 创建队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定交换机
String routingKey = "error";
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, routingKey);
// 定义一个消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 收到消息后的回调
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body);
System.out.println("consumer1->" + msg);
}
};
// 监听队列
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
/**
* 消费者2(error,info,warning)
*/
public class Consumer2 {
private static final String QUEUE_NAME = "queue_exchange_direct_sms";
private static final String EXCHANGE_NAME = "exchange_direct";
public static void main(String[] args) throws IOException, TimeoutException {
// 获取连接
Connection connection = ConnectionUtils.getConnection();
// 创建通道
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 绑定交换机,多个就绑定多次
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "info");
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "warning");
// 定义一个消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 收到消息后的回调
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body);
System.out.println("consumer2->" + msg);
}
};
// 监听队列
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
}
}
从结果我们可以看到,error消息被两个消费者接收,而warning消息只被消费者2接收了。
控制台中的绑定情况。
2、主题模式
主题模式在路由模式的基础上更进一步,实现通配符进行模糊匹配的机制。
通配符:
- #:任意多个字符。(如:goods.#匹配goods.add、goods.delete...)
- *:任意单个字符。
生产者需要做的是(代码只展示修改部分):
- 修改交换机类型为topic
- 指定路由键
// 创建交换机,topic:处理带通配符路由键
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
// 指定路由键
String msg = "新商品";
channel.basicPublish(EXCHANGE_NAME, "goods.add", null, msg.getBytes());
System.out.println("send-->" + msg);
msg = "旧商品";
channel.basicPublish(EXCHANGE_NAME, "goods.delete", null, msg.getBytes());
System.out.println("send-->" + msg);
消费者则需要指定带路由键(可以带通配符)
// 消费者1
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.add");
// 消费者2
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.#");
可以看到,消费者一只接收到了goods.add的消息,而消费者2匹配的都接收到了
控制台绑定情况
3、RPC模式
两段程序不在同一个内存空间,无法直接通过方法名调用,就需要通过网络通信方式调用。对于RabbitMQ,本身就是用于消息通信。生产端发送消息,消费者获取消息并返回一个结果。实现一个请求——响应模型的RPC远程调用。
怎么实现?最简单的方法是,生产者和消费者在约定一个队列用于消费者回复消息。但是这样不利于解耦,必须要提前约定,如果修改两端都要修改。
RabbitMQ的RPC模式通过生产者直接将回复队列发送给消费者,并给消息绑定一个唯一标识,来确认回回复消息所对应的是哪条发送消息。从而实现解耦。
生产者需要做的是指定回复队列及唯一标识,这一步通过AMQP.BasicProperties来实现
public class Client {
private static final String QUEUE_NAME = "queue_rpc";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false,null);
//声明了Server要回复的队列。队列名称由RabbitMQ自动创建。
//这样做的好处是:每个客户端有属于自己的唯一回复队列,生命周期同客户端
String replyQueue = channel.queueDeclare().getQueue();
for (int i = 0; i < 2; i++) {
// 每条消息绑定一个唯一的id来标识
final String corrID = UUID.randomUUID().toString();
// 指定回复队列和唯一id
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.replyTo(replyQueue)
.correlationId(corrID)
.build();
channel.basicPublish("", QUEUE_NAME, false, properties, "hello rpc".getBytes());
System.out.println("send->" + corrID);
}
// 接收服务端回执的消息
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(properties.getCorrelationId() + ":成功被接收");
}
};
channel.basicConsume(replyQueue, true, consumer);
}
}
消费者接收到消息后,从属性中去出回复队列,唯一标识,进行响应。
public class Server {
private static final String QUEUE_NAME = "queue_rpc";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
DefaultConsumer c = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
//我们在将要回复的消息属性中,放入从客户端传递过来的correlateId
builder.correlationId(properties.getCorrelationId());
AMQP.BasicProperties prop = builder.build();
//因为RabbitMQ对于队列,始终存在一个默认exchange="",routingKey=队列名称的绑定关系
channel.basicPublish("", properties.getReplyTo(), prop, (new String(body) + "-回复").getBytes());
}
};
channel.basicConsume(QUEUE_NAME, true, c);
}
}
可以看到,生产者收到了响应。