springboot版本
<!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.2.RELEASE</version> <type>pom</type> <!-- import 导入父工程的配置--> <scope>import</scope> </dependency>在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提 供了两种方式用来控制消息的投递可靠性模式。 ⚫ confirm 确认模式 ⚫ return 退回模式 rabbitmq 整个消息投递的路径为: producer—>rabbitmq broker—>exchange—>queue—>consumer ⚫ 消息从 producer 到 exchange 则会返回一个 confirmCallback 。 ⚫== 消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。== 我们将利用这两个 callback 控制消息的可靠性投递
持久化 • exchange要持久化 • queue要持久化 • message要持久化生产方确认Confirm消费方确认AckBroker高可用confirm测试 return测试
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有三种确认方式: • 自动确认:acknowledge=“none” • 手动确认:acknowledge=“manual” • 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
需要开启手动签收消息
listener: # RabbitMQ模式使用simple simple支持事务的 simple: # Consumer ACK机制:设置为手动签收 acknowledge-mode: manual prefetch: 1 # 限流,配置1 表示消费端每次向MQ拉取最大一条消息在方法上使用下面的注解,监听的队列 @RabbitListener(queues = “队列名称”)
下面的代码监听的是我们上面测试confirm的队列
package com.fs.Queuelistener; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; /** * Consumer ACK机制:默认自动签收 * 1. 设置手动签收。acknowledge="manual" * 2. 让监听器类实现ChannelAwareMessageListener接口 * 3. 如果消息成功处理,则调用channel的 basicAck()签收 * 4. 如果消息处理失败,则调用channel的basicNack()拒绝签收,broker重新发送给consumer */ @Component public class AckListener { @RabbitListener(queues = "test_Queue_Confirm") public void testAck(Message message, Channel channel) throws IOException { //得到消息的唯一deliveryTag long deliveryTag = message.getMessageProperties().getDeliveryTag(); //模拟接收到消息消费的逻辑 try{ //接收到消息进行消费 System.out.println(new String(message.getBody())); System.out.println("消息到了ACK机制中~~~"); //模拟执行逻辑错误 // int i = 1/0; //手动签收消息 /* deliveryTag:表示收到的消息的参数标签(消息的唯一id) 第二个参数:是否签收多条消息(批量签收消息) */ channel.basicAck(deliveryTag,true); }catch (Exception e){ //当我们上面的逻辑出现错误,就不会签收消息,我们在catch中就执行拒绝签收 System.out.println("消费逻辑出现异常~~~消息被Ack机制重回队列"); //拒绝签收 /* 第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue的尾部,broker会重新发送该消息给消费端,false为丢弃改消息,若设置了死信队列,就会交给死信队列 */ channel.basicNack(deliveryTag,true,false); } } }启动主启动:ConsumerSpringbootApplication 在提供方发送消息 在消费方查看消息被消费
➢ TTL 全称 Time To Live(存活时间/过期时间)。 ➢ 当消息到达存活时间后,还没有被消费,会被自动清除。 ➢ RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
➢ 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。 ➢ 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。 ➢ 如果两者都进行了设置,以时间短的为准。
将1. 队列统一过期这段代码注释放开,把其余代码注释,然后点击运行
使用这个类MessagePostProcessor来封装我们发生消息的属性参数
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以 被重新发送到另一个交换机,这个交换机就是DLX。 消息成为死信的三种情况:
队列消息长度到达限制;消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;原队列存在消息过期设置,消息到达超时时间未被消费;队列绑定死信交换机: 给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
死信队列: 1. 声明正常的队列(test_queue_dlx)和正常交换机(test_exchange_dlx) 2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx) 3. 正常队列绑定死信交换机 设置两个参数: * x-dead-letter-exchange:死信交换机名称 * x-dead-letter-routing-key:发送给死信交换机的routingkey
package com.fs.rabbitmq.config; import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /* 死信队列 死信队列: 1. 声明正常的队列(test_queue_dlx)和正常交换机(test_exchange_dlx) 2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx) 3. 正常队列绑定死信交换机 设置两个参数: * x-dead-letter-exchange:死信交换机名称 * x-dead-letter-routing-key:发送给死信交换机的routingkey */ @Configuration public class RabbitMQDeadMessageConfig { //创建自定义 死信交换机 逻辑认为是用来做死信服务的 @Bean public Exchange exchangeDlx(){ return ExchangeBuilder.topicExchange("exchange_del").build(); } //创建自定义 死信队列 逻辑认为是用来做死信服务的 @Bean public Queue queueDlx(){ return QueueBuilder.durable("queue_dlx").build(); } //将自定义的死信队列绑定在一块 @Bean public Binding bindingDlx(@Qualifier("exchangeDlx") Exchange exchange,@Qualifier("queueDlx") Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("dlx.*").noargs(); } //创建正常接收消息的交换机 @Bean public Exchange exchangeNormalDlx(){ return ExchangeBuilder.topicExchange("exchange_Normal_DLX").build(); } //创建正常接收消息的队列,绑定我们的死信交换机 @Bean public Queue queueNormalDlx(){ return QueueBuilder.durable("queue_Normal_DLX")//正常队列的名称 .withArgument("x-dead-letter-exchange","exchange_del")//设置改队列的死信交换机 .withArgument("x-dead-letter-routing-key","dlx.xf")//设置该队列的发送消息时指定的routingkey .withArgument("x-message-ttl",10000)//设置队列中消息的过期时间 .withArgument("x-max-length",10).build();//设置队列的最大容量 } // @Bean // public Queue queueNormalDlx(@Qualifier("exchangeDlx") Exchange exchange,@Qualifier("queueDlx") Queue queue){ // Map<String, Object> args = new HashMap<>(); // // set the queue with a dead letter feature // args.put("x-dead-letter-exchange", exchange);//设置该队列的死信交换机 // args.put("x-dead-letter-routing-key", "dlx.xf");//设置该队列的发送消息时指定的routingkey // args.put("x-message-ttl",10000);//设置队列中消息的过期时间 // args.put("x-max-length",10);//设置队列的最大容量 // return new Queue("queue_Normal_DLX", true, false, false, args); // } //将正常的交换机与队列绑定 @Bean public Binding bindingNormalDlx(@Qualifier("exchangeNormalDlx") Exchange exchange,@Qualifier("queueNormalDlx") Queue queue){ return BindingBuilder.bind(queue).to(exchange).with("test.dlx.#").noargs(); } }DlxListener
package com.fs.Queuelistener; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; /* 监听死信队列中的消息 */ @Component public class DlxListener { //监听死信队列 @RabbitListener(queues = "queue_dlx") public void testDlx(Message message, Channel channel) throws IOException { //得到消息唯一标识 long deliveryTag = message.getMessageProperties().getDeliveryTag(); try { //消费死信队列中的消息 System.out.println(new String(message.getBody())); //手动关闭 channel.basicAck(deliveryTag,true); }catch (Exception e){ //上面代码逻辑出现错误 e.printStackTrace(); //拒绝接收,从新发送 channel.basicNack(deliveryTag,true,true); } } }TestDlxListener
package com.fs.Queuelistener; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; /* 监听正常队列的消息,然后异常拒收,也不返回给发送队列,使消息成为死信消息,交给死信交换机 */ @Component public class TestDlxListener { @RabbitListener(queues = "queue_Normal_DLX") public void testDlxListener(Message message, Channel channel) throws IOException { //得到笑嘻嘻唯一标识 long deliveryTag = message.getMessageProperties().getDeliveryTag(); try{ //消费消息 //模拟消费出错 int i = 1/0; //手动提交 channel.basicAck(deliveryTag,true); }catch (Exception e){ System.out.println("消息消费出现异常,拒绝签收"); //拒绝接收消息 //出错后将消息丢弃,不返回给发送队列 拒绝签收,不重回队列 requeue=false channel.basicNack(deliveryTag,true,false); } } }启动消费端的主启动
先将TestDlxListener类中的@Component注释掉 ,将注释掉的测试代码打开,后面的代码注释掉,在run
将测试长度限制的代码放开,其余代码注释,点击run
模拟业务错误,启动消费端主启动
消息成为死信的三种情况: 3. 队列消息长度到达限制; 4. 消费者拒接消费消息,并且不重回队列; 5. 原队列存在消息过期设置,消息到达超时时间未被消费;
延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。 很可惜,在RabbitMQ中并未提供延迟队列功能。 但是可以使用:TTL+死信队列 组合实现延迟队列的效果。
启动消费端主启动
run测试方法
RabbitMQ默认日志存放路径: /var/log/rabbitmq/rabbit@xxx.log 日志包含了RabbitMQ的版本号、Erlang的版本号、RabbitMQ服务节点名称、cookie的hash值、 RabbitMQ配置文件地址、内存限制、磁盘限制、默认账户guest的创建以及权限配置等等。
在使用任何消息中间件的过程中,难免会出现某条消息异常丢失的情况。对于RabbitMQ而言,可能 是因为生产者或消费者与RabbitMQ断开了连接,而它们与RabbitMQ又采用了不同的确认机制;也 有可能是因为交换器与队列之间不同的转发策略;甚至是交换器并没有与任何队列进行绑定,生产者 又不感知或者没有采取相应的措施;另外RabbitMQ本身的集群策略也可能导致消息的丢失。这个时 候就需要有一个较好的机制跟踪记录消息的投递过程,以此协助开发和运维人员进行问题的定位。 在RabbitMQ中可以使用Firehose和rabbitmq_tracing插件功能来实现消息追踪。
firehose的机制是将生产者投递给rabbitmq的消息,rabbitmq投递给消费者的消息按照指定的格式 发送到默认的exchange上。这个默认的exchange的名称为amq.rabbitmq.trace,它是一个topic类 型的exchange。发送到这个exchange上的消息的routing key为 publish.exchangename 和 deliver.queuename。其中exchangename和queuename为实际exchange和queue的名称,分别 对应生产者投递到exchange的消息,和消费者从queue上获取的消息。 注意:打开 trace 会影响消息写入功能,适当打开后请关闭。 rabbitmqctl trace_on:开启Firehose命令 rabbitmqctl trace_off:关闭Firehose命令
rabbitmq_tracing和Firehose在实现上如出一辙,只不过rabbitmq_tracing的方式比Firehose多了一 层GUI的包装,更容易使用和管理。 启用插件:rabbitmq-plugins enable rabbitmq_tracing
100%确保消息发送成功
幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任 意多次执行对资源本身所产生的影响均与一次执行的影响相同。 在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。
