主题模式(Topics):发送的消息以及与交换机与队列绑定的关系都需设置自属的路由键,并且绑定(Binding)的路由键支持通配符,生产者向交换机发送消息之后,交换机会在与之绑定的队列中寻找路由键与消息的路由键能匹配上的队列推送该消息。若没有相匹配的,则该消息丢失。
通配符说明:
*:匹配一个字符
#:匹配一个或多个字符
注意:
1、若队列与交换机的Binding中Routing Key不包含*和#,则表示相等队列推送,类似于直连交换机(Direct Exchange);
2、若队列与交换机的Binding中Routing Key为#或#.#,则表示全部队列推送,类似于扇形交换机(Fanout Exchange)。
注:路由模式( Routing)需将交换机(Exchange)类型定义为"Topic"。
主题交换机(Topic Exchange)会根据消息自身所携带的路由键(Routing Key)在所有的绑定关系中寻找,与消息的路由键相匹配的队列推送该消息。
3.1、直连交换机中,队列与交换机的绑定关系的路由键不支持通配符,但主题交换机中支持;
3.2、直连交换机中,消息推送至队列中需要消息与绑定的路由键完全一致,但主题交换机是有通配符的情况下相匹配就行。
package com.rabbitMQ.topic;
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-23
*/
public class TopicProducer {
private static final String EXCHANGE_NAME = "exchange_topic";
private static final String TOPIC_ERROR = "message.error";
private static final String ERROR_MESSAGE = "This is topic MQ, routing key is message.error!";
private static final String TOPIC_INFO = "message.info";
private static final String INFO_MESSAGE = "This is topic MQ, routing key is message.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, "topic");
// 4.发送消息
String message = ERROR_MESSAGE;
channel.basicPublish(EXCHANGE_NAME, TOPIC_ERROR, null, message.getBytes());
System.out.println("======================= Topic MQ send message end! 【Content:" + message + "】 =======================");
message = INFO_MESSAGE;
channel.basicPublish(EXCHANGE_NAME, TOPIC_INFO, null, message.getBytes());
System.out.println("======================= Topic MQ send message end! 【Content:" + message + "】 =======================");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
} finally {
// 7.关闭连接
try {
ConnectionUtils.closeConnection(channel, conn);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
}
package com.rabbitMQ.topic;
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;
/**
* 主题模式-第一个消费者
* *:代表一个任意字符
* #:代表一个或多个任意字符
* @author zhoujin
* @data 2019-1-23
*/
public class TopicFirstConsumer {
private static final String QUEUE_NAME = "queue_topic_first";
private static final String EXCHANGE_NAME = "exchange_topic";
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, "#.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("======================= First topic(#.info) 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();
}
}
}
package com.rabbitMQ.topic;
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;
/**
* 主题模式-第二个消费者
* *:代表一个任意字符
* #:代表一个或多个任意字符
* @author zhoujin
* @data 2019-1-23
*/
public class TopicSecondConsumer {
private static final String QUEUE_NAME = "queue_topic_second";
private static final String EXCHANGE_NAME = "exchange_topic";
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, "message.#");
// 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 topic(message.#) consumer received a message! 【Content:" + message + "】 =======================");
// 休眠,模拟业务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 7.手动发送反馈回执
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 8.监听队列(关闭自动反馈,即第二个参数为)
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
注: 若只在生产者代码中定义了交换机,而未在消费者中定义队列或定义了队列但未与交换机绑定,程序编译和运行都不会报错,但是消息会被丢失。因为交换机没有存储消息的能力,RabbitMQ中只有队列有存储消息的能力。
① 为何这里需要先运行生产者的代码?
因为需要先在生产者中定义交换机。若先启动消费者,则会报错提示"no exchange"错误,原因是未定义交换机!
② 为何此次生产者发送的消息丢失了?
因为此时还没有队列与之绑定,而交换机本身是不具备存储消息的功能,因此会导致消息丢失。
2.1、在Web管理页面查看队列绑定情况;
3.1、生产者控制台输出;
3.2、第一个消费者(#.info)控制台输出;
3.3、第二个消费者(message.#)控制台输出