文章目录
- 准备linux安装rabbitMQ
拉取镜像
docker pull rabbitmq:management
安装容器
docker run -d --hostname rabbitmq --name rabbitmq -e RABBITMQ_DEFAULT_USER=lailai -e RABBITMQ_DEFAULT_PASS=lailai -p 5671:5671 -p 15672:15672 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 镜像id
一、导入依赖
<!--rabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
二、配置application.properties/yml
# 应用名
spring.application.name=XXX
# rabbitmq配置信息
# ip
spring.rabbitmq.host=XXXX
# 端口
spring.rabbitmq.port=5672
# 用户名
spring.rabbitmq.username=XXX
# 密码
spring.rabbitmq.password=XXX
# 配置虚拟机
spring.rabbitmq.virtual-host=/
# 开启return机制 (用于处理一些不可路由的消息,在一些特殊的情况下,当前的exchange不存在或者指定的路由key路由不到,这时如果我们需要及时监听这种消息,就需要return机制)
spring.rabbitmq.publisher-returns=true
# 开启消息确认机制 confirm 异步
spring.rabbitmq.publisher-confirm-type=correlated
# 消息开启手动确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#最大重试传递次数
spring.rabbitmq.listener.simple.retry.max-attempts=3
#第一次和第二次尝试传递消息的间隔时间 单位毫秒
spring.rabbitmq.listener.simple.retry.initial-interval=5000ms
#最大重试时间间隔,单位毫秒
spring.rabbitmq.listener.simple.retry.max-interval=300000ms
spring.rabbitmq.listener.simple.retry.multiplier=3
#以上配置的间隔0s 5s 15s 45s
#重试次数超过上面的设置之后是否丢弃(消费者listener抛出异常,是否重回队列,默认true:重回队列, false为不重回队列(结合死信交换机))
spring.rabbitmq.listener.simple.default-requeue-rejected=true
### 模板配置
##设置为 true 后 消费者在消息没有被路由到合适队列情况下会被return监听,而不会自动删除
spring.rabbitmq.template.mandatory=true
#在单个请求中处理的消息个数,他应该大于等于事务数量(unack的最大数量)
spring.rabbitmq.listener.simple.prefetch=2
三、3种常用类型
- rabbitMQ有
fanout
广播发布模式,direct
路由模式,topic
通配符主题订阅模式,下面将逐一详细的讲解三种模式的用法及应用场景。
1.fanout广播发布模式
- config配置
public static final String FANOUT_QUEUENAME="queue_fanout";//直连型队列名字
public static final String EXCHANGE_QUEUENAME="fanout_exchange";//直连类型交换机名字
public static final String QUEUE_DIRECT="queue-direct";
/*
1.direct(广播式直连交换机)
*/
/**
* ① 声明队列
* @return
*/
@Bean
public Queue fanoutTest(){
/**
* 1.队列名字
* 2.durable:是否持久化队列
* 3.是否独享、排外的
* 4.是否自动删除(临时队列)
* 5.arguments:队列的其他属性参数(这里不需要)
*/
return new Queue(FANOUT_QUEUENAME,true,false,false);
}
/**
* ② 声明交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange(){
return ExchangeBuilder.directExchange(EXCHANGE_QUEUENAME).durable(true).build();
}
/**
* ③ 将消息队列与直连型交换机绑定
* @return
*/
@Bean
public Binding bindQueueAndExchange(){
return BindingBuilder.bind(fanoutTest()).to(fanoutExchange());
}
发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* @param message 消息内容
*/
@Override
public void sendDirect(String message) {
rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANGE_QUEUENAME, "", "测试广播式直连型模型:" + message);
}
注:
① 使用 convertSendAndReceive
方法时的结果:使用此方法,只有确定消费者接收到消息,才会发送下一条信息,每条消息之间会有间隔时间;使用 convertAndSend
方法时的结果:输出时没有顺序,不需要等待,直接运行 不会阻塞
② 发送消息方法的构造方法参数,如果没有没有路由键(routingKey)也需要传空;
接收消息
//接受队列消息
@RabbitListener(queues = RabbitmqConfig.FANOUT_QUEUENAME)
public void receiveMessage(String msg, Channel channel, Message message, Object object) {
System.out.println("fanout收到的消息msg:"+msg);
System.out.println("channel:"+channel);
System.out.println("message:"+message);
System.out.println("object:"+object);
}
2.direct路由模式
-
config配置
-
public static final String QUEUE_NAME="queue_direct";//直连型队列名字 public static final String EXCHANGE_NAME1="direct_exchange1";//直连类型交换机名字 public static final String EXCHANGE_NAME2="direct_exchange2";//直连类型交换机名字 public static final String ROUTING_KEY1="routingKey_a";//路由键1 public static final String ROUTING_KEY2="routingKey_b";//路由键2 /* 1.direct(路由交换机) */ /** * ① 声明队列 * @return */ @Bean public Queue directTest(){ /** * 1.队列名字 * 2.durable:是否持久化队列 * 3.是否独享、排外的 * 4.是否自动删除(临时队列) * 5.arguments:队列的其他属性参数(这里不需要) */ HashMap<String, Object> map = new HashMap<>(); map.put("x-message-ttl",3000);//三秒消息过期 return new Queue(QUEUE_NAME,true,false,false); } /** * ② 声明交换机1 * @return */ @Bean public DirectExchange directExchange1(){ return ExchangeBuilder.directExchange(EXCHANGE_NAME1).durable(true).build(); } /** * ③ 将消息队列与直连型交换机1绑定 * @return */ @Bean public Binding bindQueueAndExchange1(){ return BindingBuilder.bind(directTest()).to(directExchange1()).with(ROUTING_KEY1); } /** * ② 声明交换机1 * @return */ @Bean public DirectExchange directExchange2(){ return ExchangeBuilder.directExchange(EXCHANGE_NAME2).durable(true).build(); } /** * ③ 将消息队列与直连型交换机2绑定 * @return */ @Bean public Binding bindQueueAndExchange2(){ return BindingBuilder.bind(directTest()).to(directExchange2()).with(ROUTING_KEY2); }
发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 路由模式发送消息
*/
@Override
public void sendDirect() {
for (int i = 1; i <= 20; i++) {
if(i%2==0){
MessagePostProcessor messagePostProcessor = message -> {
message.getMessageProperties().setExpiration("10000");
return message;
};
rabbitTemplate.convertAndSend( DirectConfig.EXCHANGE_NAME1,DirectConfig.ROUTING_KEY1,"出现偶数数字了" ,messagePostProcessor); //不手动ack十秒过期
rabbitTemplate.convertAndSend( DirectConfig.EXCHANGE_NAME1,DirectConfig.ROUTING_KEY1,"出现偶数数字:" + i);
}else {
rabbitTemplate.convertAndSend( DirectConfig.EXCHANGE_NAME1,DirectConfig.ROUTING_KEY2,"出现奇数数字了" ); //不会到交换机和队列中。
rabbitTemplate.convertAndSend( DirectConfig.EXCHANGE_NAME2,DirectConfig.ROUTING_KEY2,"出现奇数数字:" + i);
}
}
}
接收消息
//接受队列消息
@RabbitListener(queues = DirectConfig.QUEUE_NAME)
public void receiveMessage(String msg, Channel channel, Message message, Object object) {
System.out.println("direct收到的消息msg:"+msg);
System.out.println("channel:"+channel);
System.out.println("message:"+message);
System.out.println("object:"+object);
}
3.topic通配符主题订阅模式
-
config配置
public static final String QUEUE_NAME="queue_direct";//通配符队列名字 public static final String EXCHANGE_NAME1="direct_exchange1";//通配符类型交换机名字 public static final String EXCHANGE_NAME2="direct_exchange2";//通配符类型交换机名字 public static final String ROUTING_KEY1="log.key.#";//路由键1 public static final String ROUTING_KEY2="error.key.#";//路由键2 @Bean public Queue topQueue(){ return new Queue(QUEUE_NAME,true,false,false); } @Bean public TopicExchange topicExchange1(){ return ExchangeBuilder.directExchange(EXCHANGE_NAME1).durable(true).build(); } @Bean public TopicExchange topicExchange2(){ return ExchangeBuilder.directExchange(EXCHANGE_NAME2).durable(true).build(); } @Bean public Binding bind1(){ return BindingBuilder.bind(topQueue()).to(topicExchange1()).with(ROUTING_KEY1); } @Bean public Binding bind2(){ return BindingBuilder.bind(topQueue()).to(topicExchange2()).with(ROUTING_KEY2); }
四、ack机制
/**第一个参数:当前消息的标记
* 第二个参数:是否批量进行应答
* 下面是签收
*/
Channel.basicAck(envelope.getDeliveryTag(),false);
//下面也可以拒绝签收
/**
* 第三个参数:表示决绝签收之后这个消息是否要重回队列?
*/
channel.basicNack(envelope.getDeliveryTag(),false,true);
System.out.println("=====================异常了========================");
if (message.getMessageProperties().getRedelivered()) {
System.out.println("================消息已重复处理失败,拒绝再次接收======================" + content);
// 拒绝消息,requeue=false 表示不再重新入队,如果配置了死信队列则进入死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
} else {
System.out.println("====================消息即将再次返回队列处理=========================" + content);
// requeue为是否重新回到队列,true重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
五、confirm 和 return
-
1、confirm机制,消息的确认,是指生产者投递消息之后,如果Broker收到消息,则会给生产者一个应答,生产者能接收应答,用来确定这条消息是否正常的发送到Broker,这种机制是消息可靠性投递的核心保障。confirm机制是只保证消息到达exchange,并不保证消息可以路由到正确的queue。
RabbitTemplate.ReturnsCallback returnsCallback =new RabbitTemplate.ReturnsCallback(){ /** * ReturnedMessage 参数(Message message, int replyCode, String replyText, String exchange, String routingKey) * returnedMessage * message消息对象 * replyCode 错误码 * replyText 错误信息 * exchange 交换机名称 * routingKey 路由key */ @Override public void returnedMessage(ReturnedMessage returnedMessage) { } };
-
2、return机制,用于处理一些不可路由的消息,在一些特殊的情况下,当前的exchange不存在或者指定的路由key路由不到,这时如果我们需要及时监听这种消息,就需要return机制。
RabbitTemplate.ConfirmCallback callback = new RabbitTemplate.ConfirmCallback(){ /** * * @param correlationData 相关配置信息 * @param ack 交换机是否成功收到消息 * @param cause 错误信息 */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { correlationData.getId(); } };
六、死信队列
-
死信队列过程
生产者 → 消息 → 交换机 → (死信)队列 → 变成死信 → DLX交换机 →队列 → 消费者
/**
* 死信交换机
* @return
*/
@Bean
public TopicExchange deadLetterExchange() {
return new TopicExchange("DLX_EXCHANGE_NAME");
}
/**
* 正常交换机
* @return
*/
@Bean
public TopicExchange demoExchange() {
return new TopicExchange("EXCHANGE_NAME");
}
/**
* 声明队列
* @return
*/
@Bean
public Queue deadLetterQueue() {
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 声明 死信交换机
args.put("x-dead-letter-exchange", "DLX_EXCHANGE_NAME");
// 十秒过期变成死信
args.put("x-message-ttl",10000);
// x-dead-letter-routing-key 声明 死信路由键(这里没有声明,后面绑定的)
// args.put("x-dead-letter-routing-key", "dlx.#");
return QueueBuilder.durable("QUEUE_NAME").withArguments(args).build();
}
/**
* 死信队列绑定交换机
* @param deadLetterQueue
* @param demoExchange
* @return
*/
@Bean
public Binding bindingExchange1(Queue deadLetterQueue,TopicExchange demoExchange){
return BindingBuilder.bind(deadLetterQueue).to(demoExchange).with("dlx.#");
}
/**
* 替补队列
* @return
*/
@Bean
public Queue redirectQueue() {
return new Queue("REDIRECT_QUEUE_NAME");
}
/**
* 死信交换机和替补队列绑定
* @param redirectQueue
* @param deadLetterExchange
* @return
*/
@Bean
public Binding bindingExchange2(Queue redirectQueue,TopicExchange deadLetterExchange){
return BindingBuilder.bind(redirectQueue).to(deadLetterExchange).with("#");
}
附:
/**
* Construct a new queue, given a name, durability flag, and auto-delete flag, and arguments.
* @param name the name of the queue - must not be null; set to "" to have the broker generate the name.
* @param durable true if we are declaring a durable queue (the queue will survive a server restart)
* @param exclusive true if we are declaring an exclusive queue (the queue will only be used by the declarer's
* connection)
* @param autoDelete true if the server should delete the queue when it is no longer in use
* @param arguments the arguments used to declare the queue
*/
public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) {
Assert.notNull(name, "'name' cannot be null");
this.name = name;
this.actualName = StringUtils.hasText(name) ? name
: (Base64UrlNamingStrategy.DEFAULT.generateName() + "_awaiting_declaration");
this.durable = durable;
this.exclusive = exclusive;
this.autoDelete = autoDelete;
this.arguments = arguments != null ? arguments : new HashMap<>();
}
参数介绍:
1、name: 队列的名称;
2、actualName: 队列的真实名称,默认用name参数,如果name为空,则根据规则生成一个;
3、durable: 是否持久化;
4、exclusive: 是否独享、排外的;
5、autoDelete: 是否自动删除;
6、arguments:队列的其他属性参数,有如下可选项,可参看图2的arguments:
(1)x-message-ttl
:消息的过期时间,单位:毫秒;
(2)x-expires
:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
(3)x-max-length
:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
(4)x-max-length-bytes
:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息;
(5)x-overflow
:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head;
(6)x-dead-letter-exchange
:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
(7)x-dead-letter-routing-key
:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer
:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority
:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode
(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
(11)x-queue-master-locator
:在集群模式下设置镜像队列的主节点信息。
过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
(7)x-dead-letter-routing-key
:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer
:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority
:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode
(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
(11)x-queue-master-locator
:在集群模式下设置镜像队列的主节点信息。