RabbitMQ模式
RabbitMQ提供了六种模式,这六种模式就是RabbitMQ将消息发送到消费者的六种方式,方式的不同,RabbitMQ会将消息发送到不同的消费者程序。
Simple(简单模式)
一个队列,一个消费者。一个队列中的消息只能被一个消费者消费。
生产者代码
package rabbitmq.ced.pattern.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 六种模式
* Simple 简单模式
* 创建生产者
*
* @author 崔二旦
* @since now
*/
public class SimplePatternProducer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 3. 在连接中创建信道
channel = connection.createChannel();
// 声明队列,如果队列不存在会创建队列
channel.queueDeclare("simple_pattern", false, false, true, null);
// 准备要被发送的消息内容
String message = "崔二旦";
//推送消息
channel.basicPublish("", "simple_pattern", null, message.getBytes());
System.out.println("消息发送成功");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 释放关闭连接信道与连接
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者代码
package rabbitmq.ced.pattern.simple;
import com.rabbitmq.client.*;
/**
* 六种模式
* Simple 简单模式
* 创建消费者
*
* @author 崔二旦
* @since now
*/
public class SimplePatternConsumer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 3. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
System.out.println("等待接收消息......");
/*
* RabbitMQ推送给消费者消息回调接口,在该接口中用于编写如何对消息进行处理。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
* @param2 推送过来的消息的信息。其中包括真正的数据body(消息体),
* Properties(消息的属性信息)和其它信息。
*/
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("接收到队列发送来的消息:" + message);
};
/*
* rabbitmq取消该消费者对信道中队列的订阅时,调用的回调接口。
* 当我们在RabbitMQ管理界面手动删除该队列时,就会调用该接口。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
*/
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println("消息消费被中断"+consumerTag);
};
//自动应答 ,稍后介绍
boolean autoAck = true;
// 向队列注册自己
String consumerTag = channel.basicConsume("simple_pattern",
autoAck, deliverCallback, cancelCallback);
System.out.println("注册到RabbitMQ中后,RabbitMQ给的唯一标识是:"
+ consumerTag);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
} finally {
// 释放关闭连接信道与连接
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
Work(工作模式)
一个队列,多个消费者。一个队列将队列中的消息发送给多个消费者。当RabbitMQ队列有多个消费者时,队列收到的消息将以轮询的发送方式发送给消费者。每条消息只会发送给订阅列表里的一个消费者。
工作模式的消息分发还分两种方式,公平分发,不公平分发。
-
公平分发
公平分发就是轮询分发的概念,就是你一条,我一条,你一条,我一条,消费者收到的消息数量基本上一样的,公平分发是不管你处理速度快慢的。
生产者代码
package rabbitmq.ced.pattern.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.Scanner;
/**
* 六种模式
* Work 工作模式 公平分发
* 创建生产者
*
* @author 崔二旦
* @since now
*/
public class WorkPatternFairProducer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 3. 在连接中创建信道
channel = connection.createChannel();
// 声明队列,如果队列不存在会创建队列
channel.queueDeclare("work_pattern_fair", false,
false, true, null);
// 通过控制台输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("等待发送消息。。。");
while (scanner.hasNext()) {
String message = scanner.next();
// 记录即将被发送的消息信息
channel.basicPublish("", "work_pattern_fair",
null, message.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 释放关闭连接信道与连接
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者1代码
package rabbitmq.ced.pattern.work.fair;
import com.rabbitmq.client.*;
/**
* 六种模式
* Work 工作模式 公平分发
* 创建消费者1
*
* @author 崔二旦
* @since now
*/
public class WorkPatternFairConsumer1 {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection ;
Channel channel ;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 3. 在连接中创建信道
channel = connection.createChannel();
System.out.println("消费者1 等待接收消息......");
/*
* RabbitMQ推送给消费者消息回调接口,在该接口中用于编写如何对消息进行处理。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
* @param2 推送过来的消息的信息。其中包括真正的数据body(消息体),
* Properties(消息的属性信息)和其它信息。
*/
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("接收到队列发送来的消息:" + message);
};
/*
* rabbitmq取消该消费者对信道中队列的订阅时,调用的回调接口。
* 当我们在RabbitMQ管理界面手动删除该队列时,就会调用该接口。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
*/
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println("消息消费被中断" + consumerTag);
};
//自动应答 ,稍后介绍
boolean autoAck = true;
// 向队列注册自己
channel.basicConsume("work_pattern_fair",
autoAck, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
消费者2代码
package rabbitmq.ced.pattern.work.fair;
import com.rabbitmq.client.*;
/**
* 六种模式
* Work 工作模式 公平分发
* 创建消费者2
*
* @author 崔二旦
* @since now
*/
public class WorkPatternFairConsumer2 {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 3. 在连接中创建信道
channel = connection.createChannel();
System.out.println("消费者2 等待接收消息......");
/*
* RabbitMQ推送给消费者消息回调接口,在该接口中用于编写如何对消息进行处理。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
* @param2 推送过来的消息的信息。其中包括真正的数据body(消息体),
* Properties(消息的属性信息)和其它信息。
*/
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("接收到队列发送来的消息:" + message);
};
/*
* rabbitmq取消该消费者对信道中队列的订阅时,调用的回调接口。
* 当我们在RabbitMQ管理界面手动删除该队列时,就会调用该接口。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
*/
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println("消息消费被中断" + consumerTag);
};
//自动应答 ,稍后介绍
boolean autoAck = true;
// 向队列注册自己
channel.basicConsume("work_pattern_fair",
autoAck, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
发送效果展示
总结
根据控制台打印的效果就是,凡是注册到这个队列上的消费者都会收到消息,你一条,我一条,他一条,你一条,我一条,他一条,这种效果,RabbitMQ不会管你处理速度如何,就是按照这种效果发送消息。所以当某个消费者的处理很慢时,就会出现消息积压的情况。
-
不公平分发
不公平分发,是按照消费者的能力强弱来发送的,处理速度快的就多处理,处理速度慢的就少处理。这样的处理更加合理,因为不会出现一个堆积消息很忙,而另一个则是在闲着。这时就需要在消费者里调用channel.basicQos(int prefetchCount)方法,该方法是表示允许限制信道上的消费者所能保持的最大未确认消息的数量,当消费者中未确认的消息达到设置的上线时,队列就不会继续给该消费者发送消息,直到消费者有消息被确认之后,队列才会继续给该消费者发送消息。
basicQos方法介绍
/*
* 允许限制信道上的消费者所能保持的最大未确认消息的数量
* 这里设置为1,当消费者只要有一条消息未确认,RabbitMQ就不会继续给该消费者发送消息
* @param1 prefetchSize 消费者接收未确认消息的总体大小的上线,单位B,设置为0时表示没有上线
* @param2 prefetchCount 允许限制信道上的消费者所能保持的最大未确认消息的数量
* @param3 global true:信道上所有的消费者都需要遵从prefetchCount的限定值
* false:信道上新的消费者需要遵从prefetchCount的限定值
*/
channel.basicQos(0,1,true);
上述方法有几个缺省参数的方法,经常使用的是basicQos(int prefetchCount)方法,只有第二个参数,设置未确认上线数量即可。
生产者代码
package rabbitmq.ced.pattern.work.unfair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.Scanner;
/**
* 六种模式
* Work 工作模式 不公平分发
* 创建生产者
*
* @author 崔二旦
* @since now
*/
public class WorkPatternUnFairProducer {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 3. 在连接中创建信道
channel = connection.createChannel();
// 声明队列,如果队列不存在会创建队列
channel.queueDeclare("work_pattern_unfair", false, false, false, null);
// 通过控制台输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("等待发送消息。。。");
while (scanner.hasNext()) {
String message = scanner.next();
// 记录即将被发送的消息信息
channel.basicPublish("", "work_pattern_unfair",
null, message.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 释放关闭连接信道与连接
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者1代码
package rabbitmq.ced.pattern.work.unfair;
import com.rabbitmq.client.*;
/**
* 六种模式
* Work 工作模式 不公平分发
* 创建消费者1
*
* @author 崔二旦
* @since now
*/
public class WorkPatternUnFairConsumer1 {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection ;
Channel channel ;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 3. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
System.out.println("消费者1 等待接收消息......");
/*
* RabbitMQ推送给消费者消息回调接口,在该接口中用于编写如何对消息进行处理。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
* @param2 推送过来的消息的信息。其中包括真正的数据body(消息体),
* Properties(消息的属性信息)和其它信息。
*/
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
System.out.println("接收到队列发送来的消息:" + message);
//手动应答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
/*
* rabbitmq取消该消费者对信道中队列的订阅时,调用的回调接口。
* 当我们在RabbitMQ管理界面手动删除该队列时,就会调用该接口。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
*/
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println("消息消费被中断"+consumerTag);
};
/*
* 允许限制信道上的消费者所能保持的最大未确认消息的数量
* 这里设置为1,当消费者只要有一条消息未确认,RabbitMQ就不会继续给该消费者发送消息
* @param1 prefetchSize 消费者接收未确认消息的总体大小的上线,单位B,设置为0时表示没有上线
* @param2 prefetchCount 允许限制信道上的消费者所能保持的最大未确认消息的数量
* @param3 global true:信道上所有的消费者都需要遵从prefetchCount的限定值
* false:信道上新的消费者需要遵从prefetchCount的限定值
*/
channel.basicQos(0,1,false);
//自动应答 ,稍后介绍
boolean autoAck = false;
// 向队列注册自己
String consumerTag = channel.basicConsume("work_pattern_unfair",
autoAck, deliverCallback, cancelCallback);
System.out.println("注册到RabbitMQ中后,RabbitMQ给的唯一标识是:"
+ consumerTag);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
消费者2代码
package rabbitmq.ced.pattern.work.unfair;
import com.rabbitmq.client.*;
/**
* 六种模式
* Work 工作模式 不公平分发
* 创建消费者2
*
* @author 崔二旦
* @since now
*/
public class WorkPatternUnFairConsumer2 {
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection ;
Channel channel ;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 3. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
System.out.println("消费者2 等待接收消息......");
/*
* RabbitMQ推送给消费者消息回调接口,在该接口中用于编写如何对消息进行处理。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
* @param2 推送过来的消息的信息。其中包括真正的数据body(消息体),
* Properties(消息的属性信息)和其它信息。
*/
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody());
//这里会让程序睡眠4秒,模拟处理速度慢。
try {
Thread.sleep(1000*4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("接收到队列发送来的消息:" + message);
//手动应答
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
/*
* rabbitmq取消该消费者对信道中队列的订阅时,调用的回调接口。
* 当我们在RabbitMQ管理界面手动删除该队列时,就会调用该接口。
* @param1 消费者注册到RabbitMQ之后,RabbitMQ给生成的一个该消费者的唯一标识
*/
CancelCallback cancelCallback = (consumerTag) -> {
System.out.println("消息消费被中断"+consumerTag);
};
/*
* 允许限制信道上的消费者所能保持的最大未确认消息的数量
* 这里设置为1,当消费者只要有一条消息未确认,RabbitMQ就不会继续给该消费者发送消息
* @param1 prefetchSize 消费者接收未确认消息的总体大小的上线,单位B,设置为0时表示没有上线
* @param2 prefetchCount 允许限制信道上的消费者所能保持的最大未确认消息的数量
* @param3 global true:信道上所有的消费者都需要遵从prefetchCount的限定值
* false:信道上新的消费者需要遵从prefetchCount的限定值
*/
channel.basicQos(0,1,false);
//自动应答 ,稍后介绍
boolean autoAck = false;
// 向队列注册自己
String consumerTag = channel.basicConsume("work_pattern_unfair",
autoAck, deliverCallback, cancelCallback);
System.out.println("注册到RabbitMQ中后,RabbitMQ给的唯一标识是:"
+ consumerTag);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
发送效果展示
总结
上面介绍了工作模式的两种类型,公平分发和不公平分发,在使用时建议使用不公平分发,这样会充分的利用消费者服务器的性能,不会造成不必要的空闲浪费。
Publish/Subscribe(订阅模式)
订阅模式队列将消息分发给多个消费者,每个消费者都对应有自己的队列,当消息发送到交换机,交换机会将消息发送给所有的队列中,RabbitMQ将队列中的消息分发给指定的消费者。
生产者代码
package rabbitmq.ced.pattern.subscribe.fanout;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 六种模式
* 发布订阅模式
* <p>
* 创建生产者
*
* @author 崔二旦
* @since now
*/
public class Producer {
public static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 创建交换机,类型为fanout
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 通过控制台输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("等待发送消息。。。");
while (scanner.hasNext()) {
String message = scanner.next();
// 记录即将被发送的消息信息
channel.basicPublish(EXCHANGE_NAME, "",
null, message.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
}
}
}
消费者1代码
package rabbitmq.ced.pattern.subscribe.fanout;
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
/**
* 六种模式
* 发布订阅模式
* 创建消费者1
*
* @author 崔二旦
* @since now
*/
public class Consumer1 {
public static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 6. 创建临时队列,非持久、独占、自动删除队列
String queueName = channel.queueDeclare().getQueue();
// 7. 绑定
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("消费者1 等待接收消息......");
// 9. 消息接收成功处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到队列发送来的消息:" + message);
};
// 9. 消息接收中断处理逻辑
CancelCallback cancelCallback = (consumerTag) ->
System.out.println("消息消费被中断" + consumerTag);
// 8. 接收消息
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
消费者2代码
代码和消费者1一样的
package rabbitmq.ced.pattern.subscribe.fanout;
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
/**
* 六种模式
* 发布订阅模式
* 创建消费者2
*
* @author 崔二旦
* @since now
*/
public class Consumer2 {
public static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
// 6. 创建临时队列,非持久、独占、自动删除队列
String queueName = channel.queueDeclare().getQueue();
// 7. 绑定
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("消费者2 等待接收消息......");
// 9. 消息接收成功处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到队列发送来的消息:" + message);
};
// 9. 消息接收中断处理逻辑
CancelCallback cancelCallback = (consumerTag) ->
System.out.println("消息消费被中断" + consumerTag);
// 8. 接收消息
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
发送效果展示
总结
发布订阅模式,根据上面的例子可以看出,生产者将消息发送到交换机,交换机再将消息路由到绑定到该交换机的队列上,队列再将消息发送到注册的消费者程序。在看上面的代码时,你会发现生产者并没有声明队列等信息,只是声明了一个Fanout的交换机,生产者将消息发送到交换机就结束了。而在两个消费者的代码中,都创建了一个临时的队列,并将临时队列绑定到了生产者创建的交换机上,这样交换机就会将消息路由到绑定的队列上了,队列再将消息发送到消费者上。
代码编写逻辑解释
以前
以前的代码生产者负责创建交换机、队列、绑定,消费者只负责注册自己到队列。
现在
现在代码生产者只负责创建交换机,消费者负责创建队列、绑定、注册自己到队列。
注:这里的代码解释只是说在写代码时的变化情况,和消息的路由流转不是一个概念。
Routing(路由模式)
路由模式是发布订阅模式的升级,每个消费者也是有自己指定的队列,只是在交换机将消息发送到队列中时需要进行规则匹配,只有完全匹配之后,RabbitMQ才会将消息分发给队列对应的消费者。
交换机类型设置为Direct类型
生产者代码
package rabbitmq.ced.pattern.subscribe.direct;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 六种模式
* 发布订阅模式 直接
* 路由模式
* <p>
* 创建生产者
*
* @author 崔二旦
* @since now
*/
public class Producer {
public static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 创建交换机,类型为direct
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 通过控制台输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("等待发送消息。。。");
while (scanner.hasNext()) {
String message = scanner.nextLine();
String routingKey = getRoutingKey(message);
// 记录即将被发送的消息信息
channel.basicPublish(EXCHANGE_NAME, routingKey,
null, message.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
}
}
/**
* 截取消息前三个字符当作routingKey
* @param message 消息
* @return routingKey
*/
public static String getRoutingKey(String message){
if(message.length() < 1){
return "ced";
}else{
return message.substring(0,3);
}
}
}
消费者1代码 ced
package rabbitmq.ced.pattern.subscribe.direct;
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
/**
* 六种模式
* 发布订阅模式 direct
* 路由模式
* 创建消费者1
*
* @author 崔二旦
* @since now
*/
public class Consumer1 {
public static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 6. 创建临时队列,非持久、独占、自动删除队列
String queueName = channel.queueDeclare().getQueue();
// 7. 绑定 ced
channel.queueBind(queueName, EXCHANGE_NAME, "ced");
System.out.println("消费者1 等待接收消息......");
// 9. 消息接收成功处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到队列发送来的消息:" + message);
};
// 9. 消息接收中断处理逻辑
CancelCallback cancelCallback = (consumerTag) ->
System.out.println("消息消费被中断" + consumerTag);
// 8. 接收消息
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
消费者2代码 clr
package rabbitmq.ced.pattern.subscribe.direct;
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
/**
* 六种模式
* 发布订阅模式 direct
* 路由模式
* 创建消费者2
*
* @author 崔二旦
* @since now
*/
public class Consumer2 {
public static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 6. 创建临时队列,非持久、独占、自动删除队列
String queueName = channel.queueDeclare().getQueue();
// 7. 绑定 clr
channel.queueBind(queueName, EXCHANGE_NAME, "clr");
System.out.println("消费者2 等待接收消息......");
// 9. 消息接收成功处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到队列发送来的消息:" + message);
};
// 9. 消息接收中断处理逻辑
CancelCallback cancelCallback = (consumerTag) ->
System.out.println("消息消费被中断" + consumerTag);
// 8. 接收消息
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
发送效果展示
总结
路由模式其实是发布订阅模式的升级版,发布订阅是使用Fanout类型的交换机,fanout类型的交换机会将消息路由到所有与之绑定的队列中,而路由模式是不要发送给所有绑定的队列中,而是有一个判断条件,此时就需要使用Direct型交换机了,这样RabbitMQ就会根据生产者发送过来的消息中的RoutingKey进行比对,交换机会将消息路由到队列,队列在将消息发送到注册的消费者程序。
当我们将RoutingKey 都设置成一样的话,产生的效果其实和发布订阅模式是一样的。
Topic(主题模式)
主题模式是Routing(路由模式)的一个升级,在路由规则中加入了模糊匹配规则,根据模糊匹配的结果将消息分发到相应的消费者。模糊匹配的规则是符号“#”匹配一个或多个词,符号“*”只能匹配一个词。
交换机类型设置为Topic类型
生产者代码
package rabbitmq.ced.pattern.subscribe.topic;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 六种模式
* 发布订阅模式 主题
* 主题模式
* <p>
* 创建生产者
*
* @author 崔二旦
* @since now
*/
public class Producer {
public static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 创建交换机,类型为topic
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 通过控制台输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("等待发送消息。。。");
while (scanner.hasNext()) {
String message = scanner.nextLine();
String routingKey = getRoutingKey(message);
// 记录即将被发送的消息信息
channel.basicPublish(EXCHANGE_NAME, routingKey,
null, message.getBytes(StandardCharsets.UTF_8));
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
}
}
/**
* 截取消息前三个字符当作routingKey
* @param message 消息
* @return routingKey
*/
public static String getRoutingKey(String message){
if(message.length() < 1){
return "love.ced.nb";
}else{
return message.substring(0,11);
}
}
}
消费者1代码 *.ced.nb
package rabbitmq.ced.pattern.subscribe.topic;
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
/**
* 六种模式
* 发布订阅模式 topic
* 主题模式
* 创建消费者1
*
* @author 崔二旦
* @since now
*/
public class Consumer1 {
public static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 6. 创建临时队列,非持久、独占、自动删除队列
String queueName = channel.queueDeclare().getQueue();
// 7. 绑定 ced
channel.queueBind(queueName, EXCHANGE_NAME, "*.ced.nb");
System.out.println("消费者1 等待接收消息......");
// 9. 消息接收成功处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到队列发送来的消息:" + message);
};
// 9. 消息接收中断处理逻辑
CancelCallback cancelCallback = (consumerTag) ->
System.out.println("消息消费被中断" + consumerTag);
// 8. 接收消息
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
消费者2代码 *.clr.*
package rabbitmq.ced.pattern.subscribe.topic;
import com.rabbitmq.client.*;
import java.nio.charset.StandardCharsets;
/**
* 六种模式
* 发布订阅模式 topic
* 主题模式
* 创建消费者2
*
* @author 崔二旦
* @since now
*/
public class Consumer2 {
public static final String EXCHANGE_NAME = "topic_exchange";
public static void main(String[] args) {
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2. 设置连接属性
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection;
Channel channel;
try {
// 3. 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者");
// 4. 在连接中创建信道,RabbitMQ中的所有操作都是在信道中完成的
channel = connection.createChannel();
// 5. 声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 6. 创建临时队列,非持久、独占、自动删除队列
String queueName = channel.queueDeclare().getQueue();
// 7. 绑定 clr
channel.queueBind(queueName, EXCHANGE_NAME, "*.clr.*");
System.out.println("消费者2 等待接收消息......");
// 9. 消息接收成功处理逻辑
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println("接收到队列发送来的消息:" + message);
};
// 9. 消息接收中断处理逻辑
CancelCallback cancelCallback = (consumerTag) ->
System.out.println("消息消费被中断" + consumerTag);
// 8. 接收消息
channel.basicConsume(queueName, true, deliverCallback, cancelCallback);
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息接收异常");
}
}
}
发送效果展示
总结
主题模式是路由模式的升级版,路由模式是完整匹配,而主题模式是可以模糊匹配的,在处理业务的时候,更加灵活一点,但是可能会比其它模式会慢一点,毕竟逻辑复杂了些。所以在选择那种模式的时候,按照需要去选择就可以了。
RPC(模式)
RPC(远程过程调用)因基本上用不到,所以略。
模式总结
当你把上面的各种模式的代码,从头到尾写一遍之后,你会感觉到所谓的模式,其实都是使用各种类型的交换机实现出的效果。所以交换机的概念是比较重要的,而且在使用发布订阅模式、路由模式、主题模式的时候,你会更能理解生产者只是把消息发送到交换机,之后消息的走向,被放到那个队列中,生产者是不知道的。消费者想要接收那个队列的消息,只要把自己注册到RabbitMQ队列中即可,这样队列就可以把消息发送到相应的消费者了。
到此RabbitMQ的模式就介绍完毕了!