RabbitMQ->路由订阅模型(direct)
思考
广播模型虽然很方便,但是涉及到分别对待处理的时候,就不能处理了。
试想:
班级中,老师通知大家有集体活动,所有学生都接收到此消息。
但是要分配任务,就给部分学 生去分配任务->比如通知班长去统计要参加的人,通知组长传达详细内容 等等
这时广播模式就不是很适用了,因为有分级权限和对应的消息,要传达给不同身份的人。有一些内容是不能让所有人都知道的,而是其中的一部分。
因此路由订阅模型(direct) 就出现了,非常适合这种场景
设计实验
我们根据此模型,设计出这个实验模型
其中
消费者1 绑定 Info
消费者2 绑定 Info、Waning
消费者3 绑定 Info、Warning、Error
这样,消息生产者发送
路由键值绑定到 Info 的消息的时候,三个消费者都能收到消息。
路由键值绑定到 Warning 的消息的时候,消费者2、消费者3 能就收到消息
路由键值绑定到 Error 的消息的时候,只有消费者3能接收到消息
实现了消息分级传递的场景
下面我们使用代码实现
代码实现
首先创建这个实验要用到很多地方的一些数据:交换机的名称、交换机的类型、路由键值、消息等内容,将其存储在一个专门存储常量的类里面
Step1 创建存储常量的类 Constant
public class Constant {
/** 交换机的名字 */
public static final String EXCHANGE_NAME = "log_direct";
/** 交换机的类型 */
public static final String EXCHANGE_TYPE = "direct";
/** 路由键值 */
/** routingKey:info */
public static final String ROUTING_KEY_INFO = "info";
/** routingKey:warning */
public static final String ROUTING_KEY_WARNING = "warning";
/** routingKey:warning */
public static final String ROUTING_KEY_ERROR = "error";
/** 消息内容 */
/** INFO */
public static final String MESSAGE_INFO = ">[消息] 这条消息通过 路由键值["+ ROUTING_KEY_INFO +"] 获取到的消息";
/** WARNING */
public static final String MESSAGE_WARNING = ">[消息] 这条消息通过 路由键值["+ ROUTING_KEY_WARNING +"] 获取到的消息";
/** ERROR */
public static final String MESSAGE_ERROR = ">[消息] 这条消息通过 路由键值["+ ROUTING_KEY_ERROR +"] 获取到的消息";
}
Step2 创建连接的工具类 ConnectionUtil
工具类功能为 创建连接,销毁资源
public class ConnectionUtil {
/** Ip 地址 */
private static final String IP_ADDRESS = "127.0.0.1";
/** 固定端口 */
private static final int PORT = 5672;
// TODO 改成你自己的用户名
/** 用户名 */
private static final String USER_NAME = "root";
// TODO 改成你自己的密码
/** 密码 */
private static final String PASSWORD = "dcpnet";
/**
* 获取连接
* @return 连接
* @throws IOException IOException
* @throws TimeoutException TimeOutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
/** 创建连接工厂 */
ConnectionFactory connectionFactory = new ConnectionFactory();
// 设置连接工厂属性
// IP 地址
connectionFactory.setHost(IP_ADDRESS);
// 端口
connectionFactory.setPort(PORT);
// 用户名
connectionFactory.setUsername(USER_NAME);
// 密码
connectionFactory.setPassword(PASSWORD);
// 创建新的连接并且返回
return connectionFactory.newConnection();
}
/**
* 关闭资源
* @param connection 连接
* @param channel 信道
*/
public static void close(Connection connection, Channel channel){
try {
channel.close();
connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
Step3 创建生产者 ProducerClient
功能:发送不同的消息,分别到 Info、Warning、Error 路由键值绑定的队列
public class ProducerClient {
public static void main(String[] args) throws Exception{
// 获得一个连接
Connection connection = ConnectionUtil.getConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 声明交换机 Param1:交换机的名称 Param2:交换机的类型
// TODO 没有创建交换机的话,将下面这句话取消注释
//channel.exchangeDeclare(Constant.EXCHANGE_NAME,Constant.EXCHANGE_TYPE);
// 发布消息
// INFO 消息
channel.basicPublish(Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO,null,Constant.MESSAGE_INFO.getBytes());
// WARNING 消息
channel.basicPublish(Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_WARNING,null,Constant.MESSAGE_WARNING.getBytes());
// ERROR 消息
channel.basicPublish(Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_ERROR,null,Constant.MESSAGE_ERROR.getBytes());
// 关闭资源
ConnectionUtil.close(connection,channel);
}
}
Step4 创建消费者1、2、3 ConsumerClient1、2、3
ConsumerClient1
这个消费者只绑定了 info 的路由键值到交换机
public class ConsumerClient1 {
public static void main(String[] args) throws Exception{
// 获取连接
Connection connection = ConnectionUtil.getConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 创建一个临时队列,返回队列的名字
String queueName = channel.queueDeclare().getQueue();
// 绑定 info 键值
channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO);
// 消费消息
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("<[消费者1] 接收到消息" + new String(body));
}
});
}
}
ConsumerClient2
这个消费者绑定了 Info、Warning 的路由键值到交换机
public class ConsumerClient2 {
public static void main(String[] args) throws Exception{
// 获取连接
Connection connection = ConnectionUtil.getConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 创建一个临时队列,返回队列的名字
String queueName = channel.queueDeclare().getQueue();
// 绑定 info 键值
channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO);
//绑定 waning 键值
channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_WARNING);
// 消费消息
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("<[消费者2] 接收到消息" + new String(body));
}
});
}
}
ConsumerClient3
这个消费者绑定了 Info、Warning、Error 的路由键值到交换机
public class ConsumerClient3 {
public static void main(String[] args) throws Exception{
// 获取连接
Connection connection = ConnectionUtil.getConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 创建一个临时队列,返回队列的名字
String queueName = channel.queueDeclare().getQueue();
// 绑定 info 键值
channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_INFO);
// 绑定 warning 键值
channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_WARNING);
// 绑定 error 键值
channel.queueBind(queueName,Constant.EXCHANGE_NAME,Constant.ROUTING_KEY_ERROR);
// 消费消息
channel.basicConsume(queueName,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("<[消费者3] 接收到消息" + new String(body));
}
});
}
}
激动人心的时候终于到了!!!
Step5 运行程序
首先确保RabbitMQ 中有 log_direct 交换机,如果没有请创建:
方式一:将生产者中 注释了 TODO 的下面的那行代码取消注释运行生产者
方式二:在web端创建
首先运行三个消费者 ConcumerClient1、2、3
运行生产者 ProducerClient
生产者程序运行成功后,可以看到三个消费者的控制台输出了
消费者1 ->
消费者2 ->
消费者3 ->
可以看到,绑定了响应路由键到交换机的消费者,都受到了对应的消息。
加油!让你的努力配得上你想要的生活!!!