RabbitMQ作为一款常用的消息中间件,在微服务项目中得到大量应用,其本身是微服务中的重点和难点,有不少概念我自己的也是一知半解,本系列博客尝试结合实际应用场景阐述RabbitMQ的应用,分析其为什么使用,并给出怎么用的案例。
本篇博客结合场景来阐述RabbitMQ的几种模式,描述了不同模式的应用场景,并给出相应的代码。(文末有惊喜~)
RabbitMQ基础(1)——生产者消费者模型 & RabbitMQ简介 & Docker版本的安装配置 & RabbitMQ的helloworld + 分模块构建 & 解决大量注册案例
RabbitMQ基础(2)——发布订阅/fanout模式 & topic模式 & rabbitmq回调确认 & 延迟队列(死信)设计
RabbitMQ的Docker版本安装 + 延迟插件安装 & QQ邮箱和阿里云短信验证码的主题模式发送
1.MQ,消息队列的应用场景,几种MQ简单对比;
2.分析RabbitMQ的浏览器控制台页面;
3.结合场景来阐述RabbitMQ的几种模式,描述了不同模式的应用场景,并给出相应的代码;
Message Queue 消息队列。
有比较大的负载,而且这些负载不用立刻马上给程序返回结构,可以有一些等待时间。可以用消息队列。
过去的项目,大部分都是同步解决问题。
UserinfoService{
register(){
//典型的同步
//插入数据库
//发短信
//发邮件
}
}
//如果你的操作基本没有任何延时操作,或者瓶颈,没有压力。那么你没有必要用MQ
UserinfoService{
register(){
//发起的是一个异步请求
都不等待对方给我返回的结果
//异步插入数据库
//异步发短信
//异步发邮件
}
}
库存有没有可能挂了。或者访问量巨大。因为库存慢,导致订单也慢。
解耦。
双12的时候,叫好早上的8点。秒杀的时候。
有些瞬间服务器压力是超大的,过了这个瞬间,几乎没有消耗量。
等服务器能够正常的时候,我慢慢执行就行了。
MQ的前身,就是一个发布者订阅模式。
Kafka RabbitMQ(1W/s) RocketMQ ActiveMQ
Kafka : 10w/S 主要用于日志。
Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。
包括刷新时间间隔,rabbitmq节点,连接端口的信息等
配置可以导出和导入
包括队列的名字,状态,准备好的消息数量,未确认的消息数量,总计消息数量
使用的依赖
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.7.3version>
dependency>
package com.tianju.config;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 建立连接的工具类
*/
public class ConnectionFactory {
public static Connection createConnection() throws IOException, TimeoutException {
com.rabbitmq.client.ConnectionFactory connectionFactory = new com.rabbitmq.client.ConnectionFactory();
connectionFactory.setHost("192.168.111.130"); // http://192.168.111.130/
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("123");
connectionFactory.setVirtualHost("/demo");
// amqp://[email protected]:5672/
return connectionFactory.newConnection();
}
}
package com.tianju.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.tianju.config.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者发送消息
* 建立连接--> 创建频道 --> 创建队列 --> 发送消息
*/
public class Provider {
private static String QUEUE_ORDER = "queue_order";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
channel.queueDeclare(QUEUE_ORDER,false,false,false,null);
// 发送消息,指定给哪个队列上发消息
for (int i = 0; i < 100; i++) {
String msg = "hello rabbitmq--"+i;
channel.basicPublish("", QUEUE_ORDER, null, msg.getBytes());
System.out.println("消息发布成功");
}
connection.close();
}
}
package com.tianju.simple;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static String QUEUE_ORDER = "queue_order";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
channel.queueDeclare(QUEUE_ORDER,false,false,false,null);
// 队列必须声明,如果不存在,则自动创建
// 声明一个消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 回调函数,用来接收消息
* @param consumerTag the consumer tag associated with the consumer
* @param envelope packaging data for the message
* @param properties content header data for the message
* @param body the message body (opaque, client-specific byte array),字节数组
* @throws IOException
*/
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body); // 收到的信息
System.out.println("消费者接收到:"+msg);
try {
Thread.sleep(3000);// 模拟一个耗时操作
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
// 表明自己是消费者,接收消息
/**
* autoAck:自动确认设置成 true
*/
channel.basicConsume(QUEUE_ORDER, true, defaultConsumer);
}
}
消费者进行消费,假设突然之间服务宕机了,此时消费了4条消息,理论上还应该有96条消息
打开控制台页面查看,消息全部消失了,出现了数据丢失的情况
!
全部用默认值的情况下,如果发生异常,则消息全部丢失。
消费者一次性拿到了所有的消息。
生产者 ==投递消息=》队列
消费者=接受==》队列
ack: false 必须要手工确认。
消费者接到这个消息的时候, 这个消息进入 unack状态。
1:手工确认。消息删除。完成
2: 没手工确认,断开连接或者超时。
3:这个消息重新进入ready状态。等待其他消费者进行消费。
先设置自动ack为false,表示需要手工确认,然后在消费消息的方法中,进行消息的确认。
package com.tianju.simple;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static String QUEUE_ORDER = "queue_order";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
channel.queueDeclare(QUEUE_ORDER,false,false,false,null);
// 队列必须声明,如果不存在,则自动创建
// 声明一个消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 回调函数,用来接收消息
* @param consumerTag the consumer tag associated with the consumer
* @param envelope packaging data for the message
* @param properties content header data for the message
* @param body the message body (opaque, client-specific byte array),字节数组
* @throws IOException
*/
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
// no work to do
String msg = new String(body); // 收到的信息
System.out.println("消费者接收到:"+msg);
try {
Thread.sleep(3000);// 模拟一个耗时操作
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 消费者代码可能有失败,消息拿到之后,可能还没有处理,就宕机了
// 消息确认的代码一定在最后一行
// long deliveryTag 消息的下标;
// boolean multiple 是否批量确认;
channel.basicAck(envelope.getDeliveryTag(),true); // 确认消息,批量确认
}
};
// 表明自己是消费者,接收消息
/**
* autoAck:自动确认设置成 true
* 是否自动确认,
* false:不进行自动确认;true:自动确认
* 消费过程中可能产生异常
* 如果产生异常,则必须进行消费补偿
*/
channel.basicConsume(QUEUE_ORDER, false, defaultConsumer); // 接收消息
}
}
再次模拟宕机的情况,一开始消息全部被放到Unack中,当宕机时,又把消息吐了出来,至少消息没有出现丢失的情况。
两个消费者消费的是同一个队列中的消息。
两个消费者上来后,默认是按照平均分配。结果: 有人瞬间干完。有人很久都没干完。
可以限制你一次拉取几个。
这样两个消费者,也能够节省服务器CPU了。
// 创建频道
Channel channel = connection.createChannel();
channel.basicQos(1); /** 1次只能拿1个 **/
当消费的速度太慢,觉得不够,加入其他的消费者。
核心,只有一个消息队列。
消息是轮询的方式,发送给不同的消费者。
队列的参数发生变化后,要删除再添加。
// 创建频道
Channel channel = connection.createChannel();
channel.queueDelete(QUEUE_ORDER); // 先删除旧的队列
boolean durable = true; // 当前队列中的消息持久化操作,重启之后,消息还在
// 创建队列
channel.queueDeclare(QUEUE_ORDER,durable,false,false,null);
package com.tianju.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 生产者发送消息
* 建立连接--> 创建频道 --> 创建队列 --> 发送消息
*/
public class Provider {
private static String QUEUE_ORDER = "queue_order";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
channel.queueDelete(QUEUE_ORDER); // 先删除旧的队列
boolean durable = true; // 当前队列中的消息持久化操作,重启之后,消息还在
Map map = new HashMap();
map.put("x-max-length", 10); // 设置最大的长度,只接受10个
// 创建队列
channel.queueDeclare(QUEUE_ORDER,durable,false,false,map);
// 发送消息,指定给哪个队列上发消息
for (int i = 0; i < 1000; i++) {
String msg = "hello rabbitmq--"+i;
channel.basicPublish("", QUEUE_ORDER, null, msg.getBytes());
System.out.println("消息发布成功");
}
connection.close();
}
}
How many (ready) messages a queue can contain before it starts to drop them from its head.
Sets the queue overflow behaviour. This determines what happens to messages when the maximum length of a queue is reached. Valid values are
drop-head
,reject-publish
orreject-publish-dlx
. The quorum queue type only supportsdrop-head
andreject-publish
.
package com.tianju.work;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 生产者发送消息
* 建立连接--> 创建频道 --> 创建队列 --> 发送消息
*/
public class Provider {
private static String QUEUE_ORDER = "queue_order";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
channel.queueDelete(QUEUE_ORDER); // 先删除旧的队列
boolean durable = true; // 当前队列中的消息持久化操作,重启之后,消息还在
Map map = new HashMap();
map.put("x-max-length", 10); // 设置最大的长度,只接受10个
map.put("x-overflow", "reject-publish"); // 拒绝发布,变成前10ge
map.put("x-max-length-bytes", 4); // 消息的最大字节长度
// 创建队列
channel.queueDeclare(QUEUE_ORDER,durable,false,false,map);
// 发送消息,指定给哪个队列上发消息
for (int i = 0; i < 20; i++) {
String msg = "53"+i;
channel.basicPublish("", QUEUE_ORDER, null, msg.getBytes());
msg = "hello rabbitmq--"+i;
channel.basicPublish("", QUEUE_ORDER, null, msg.getBytes());
System.out.println("消息发布成功");
}
connection.close();
}
}
参数
//创建队列
boolean durable = true;//当前队列中的消息进行持久化操作 重启之后,消息还在
Map map = new HashMap();
map.put("x-max-length",10);//设置队列的最大长度
map.put("x-overflow","reject-publish");//设置队列的最大长度 drop head
map.put("x-max-length-bytes",4);//设置消息的最大字节长度
// map.put("x-expires",10000);//超时后,直接删除队列
channel.queueDeclare(QUQUENAME,durable,false,false,map);
工作模式中,只有一个队列。两个消费者轮询从同一个队列中取数据。
发布者订阅模式: 交换机后面会绑定多个消息队列。每个消息队列都有完整的信息。每个队列后的消费者都会有完整的消息。
交换机:就是一个无意识的广播。行为就是扇出.
场景:
下订单:
1:订单的数据入数据库
2:发送订单的短信
3: 物流
要每个队列都有完整的信息。
package com.tianju.publish;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者发送消息
* 建立连接--> 创建频道 --> 创建队列 --> 发送消息
*/
public class Provider {
private static String QUEUE_ORDER = "queue_order";
private static String EXCHANGE = "pet_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "fanout"); // 扇出,类型只能用这个
for (int i = 0; i < 100; i++) {
channel.basicPublish(EXCHANGE, "", MessageProperties.TEXT_PLAIN, ("hello fanout"+i).getBytes());
}
}
}
package com.tianju.publish;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static String EXCHANGE = "pet_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "fanout"); // 扇出,类型只能用这个
channel.queueDeclare("q32", false, false, false, null);
channel.queueBind("q32", EXCHANGE, ""); // 路由键fanout模式,必须为空,即使写了是无效的
channel.basicConsume("q32", new DefaultConsumer(channel){
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body);
System.out.println("消费者1:"+msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
package com.tianju.publish;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer2 {
private static String EXCHANGE = "pet_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "fanout"); // 扇出,类型只能用这个
channel.queueDeclare("q321", false, false, false, null);
channel.queueBind("q321", EXCHANGE, ""); // 路由键fanout模式,必须为空,即使写了是无效的
channel.basicConsume("q321", new DefaultConsumer(channel){
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body);
System.out.println("消费者2:"+msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
允许用不同的路由键来接受不同的信息。
队列会接到完整的全部的信息。
日志系统:
日志级别:
OFF INFO DEBUG ERROR FATAL ALL
如果是致命错误: 立刻给运维发短信。
全部信息: 放到专门的日志系统
Error: 进行发邮件
package com.tianju.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者发送消息
* 建立连接--> 创建频道 --> 创建队列 --> 发送消息
*/
public class Provider {
private static String EXCHANGE = "exchange_58";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "direct"); // 直接交换机
String msg = "this is fatal message";
channel.basicPublish(EXCHANGE,"fatal", MessageProperties.TEXT_PLAIN,msg.getBytes());
msg = "this is error message";
channel.basicPublish(EXCHANGE,"error", MessageProperties.TEXT_PLAIN,msg.getBytes());
msg = "this is debug message";
channel.basicPublish(EXCHANGE,"debug", MessageProperties.TEXT_PLAIN,msg.getBytes());
}
}
package com.tianju.routing;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static String EXCHANGE = "exchange_58";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "direct"); // 直接交换机
// 队列必须声明,如果不存在,则自动创建
channel.queueDeclare("q58", false, false, false, null);
channel.queueBind("q58", EXCHANGE, "fatal");
channel.queueBind("q58", EXCHANGE, "error");
channel.queueBind("q58", EXCHANGE, "debug");
channel.basicConsume("q58", new DefaultConsumer(channel){
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body);
System.out.println("消费者1:"+msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
package com.tianju.routing;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer2 {
private static String EXCHANGE = "exchange_58";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "direct"); // 直接交换机
// 队列必须声明,如果不存在,则自动创建
channel.queueDeclare("q581", false, false, false, null);
channel.queueBind("q581", EXCHANGE, "fatal");
channel.basicConsume("q581", new DefaultConsumer(channel){
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body);
System.out.println("消费者1:"+msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
可以采取通配符模式:
* (star) can substitute for exactly one word.
# (hash) can substitute for zero or more words.
#`:匹配0个或多个词
*
:匹配不多不少恰好1个词
举例:
item.#
:能够匹配item.insert.abc
或者 item.insert
item.*
:只能匹配item.insert
场景:
京东: 无锡,河南下订单。退货
==========总部 收到这个订单 .
无锡分销商也有这个消息 *.wuxi
package com.tianju.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.MessageProperties;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 生产者发送消息
* 建立连接--> 创建频道 --> 创建队列 --> 发送消息
*/
public class Provider {
private static String EXCHANGE = "exchange_58";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "topic"); // 主题模式交换机
String msg = "this is wuxi order";
channel.basicPublish(EXCHANGE,"order.wuxi", MessageProperties.TEXT_PLAIN,msg.getBytes());
msg = "this is henan order";
channel.basicPublish(EXCHANGE,"order.henan", MessageProperties.TEXT_PLAIN,msg.getBytes());
msg = "this is wuxi back";
channel.basicPublish(EXCHANGE,"back.wuxi", MessageProperties.TEXT_PLAIN,msg.getBytes());
msg = "this is henan back";
channel.basicPublish(EXCHANGE,"back.henan", MessageProperties.TEXT_PLAIN,msg.getBytes());
}
}
package com.tianju.topic;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static String EXCHANGE = "exchange_58";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "topic"); // 主题交换机
// 队列必须声明,如果不存在,则自动创建
channel.queueDeclare("q58", false, false, false, null);
channel.queueBind("q58", EXCHANGE, "*.wuxi"); // 意味着无锡的订单和退货都能收到
channel.basicConsume("q58", new DefaultConsumer(channel){
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body);
System.out.println("无锡仓库:"+msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
package com.tianju.topic;
import com.rabbitmq.client.*;
import com.tianju.config.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer2 {
private static String EXCHANGE = "exchange_58";
public static void main(String[] args) throws IOException, TimeoutException {
// 建立连接
Connection connection = ConnectionFactory.createConnection();
// 创建频道
Channel channel = connection.createChannel();
// 创建队列
// 创建交换机
channel.exchangeDeclare(EXCHANGE, "topic"); // 主题交换机
// 队列必须声明,如果不存在,则自动创建
channel.queueDeclare("q581", false, false, false, null);
channel.queueBind("q581", EXCHANGE, "*.*"); // 意味着所有的订单和退货都能收到
channel.basicConsume("q581", new DefaultConsumer(channel){
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// no work to do
String msg = new String(body);
System.out.println("总部仓库:"+msg);
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
1.MQ,消息队列的应用场景,几种MQ简单对比;
2.分析RabbitMQ的浏览器控制台页面;
3.结合场景来阐述RabbitMQ的几种模式,描述了不同模式的应用场景,并给出相应的代码;