rabbitmq+springboot实现消息可靠投递

前言

文章最后附带rabbitmq生产环境的一些参数设置参考。
实现消息可靠性投递有两种方法,一个是开启事务,一个是使用确认机制,具体参考下图讲解。
在这里插入图片描述
通过上图可知消息投递失败将会发生在三个地方,生产者到交换机,交换机到队列,队列到消费者。
所以为了保证消息的可靠性,需要开启消息确认机制(confirmCallback、returnCallback)以及消费端手动确认模式(手动ack)或者消费者重试机制。

一、 配置消息确认回调机制

yml开启配置

spring:
  rabbitmq:
    host: 192.168.25.131
    port: 5672
    virtual-host: /
    #开启发送端确认,mq服务是否收到消息,收到消息触发confirmCallback方法回调
    publisher-confirm-type: correlated
    #开启发送端消息抵达队列的确认,消息是否从【交换机/其他】到达指定队列,触发returnCallback进行回调,详情看配置文件描述
    publisher-returns: true
    #只要抵达队列,以异步发送优先回调我们这个return confirm【跟publisher-returns是一组都设置成true】
    template:
      mandatory: true
    username: admin
    password: 123456

参数讲解:

  • publish-confirm-type
    开启消息从生产者到broker(mq)的确认机制,可选值:
    1、simple:同步等待confirm结果,直到超时;
    2、correlated:异步回调,定义ConfirmCallback,mq返回结果时会回调这个ConfirmCallback。
  • publish-returns
    开启消息从交换机到队列的确认机制,可选:false、true。
  • template.mandatory
    定义消息投递失败的策略,可选值:
    1、true:调用ReturnCallback;
    2、false:直接丢弃消息。

定义失败回调配置

ConfirmCallback:生产者—broker;
ReturnCallback:交换机—队列;

    public void initRabbitTemplate(){
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * @param correlationData 当前消息的唯一关联数据(这个是消息的唯一id)
             * @param ack 消息是否成功收到,只要消息抵达Broker服务器,ack就等于true,不管消息之后的状态是否成功
             * @param cause 失败的原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                /**
                 * 1、做好消息确认机制(publisher、consumer【手动ack】)
                 * 2、每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍
                 */
                if (!ack){
                    System.out.println("confirm...correlationData["+correlationData+"]==>ack["+ack+"]==>cause["+cause+"]");
                }
            }
        });

        //设置消息抵达队列的确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定的队列,就触发这个失败回调
             * @param message   投递失败的消息详细信息
             * @param replyCode 回复的状态码
             * @param replyText 回复的文本内容
             * @param exchange  当时这个消息发给哪个交换机
             * @param routingKey 当时这个消息用的哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                //报错误了。修改数据库当前消息的状态-》错误。
                System.out.println("Fail Message["+message+"]==>replyCode["+replyCode+"]==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]");
            }
        });
    }

配置完之消息投递失败就会进入对应回调方法,根据业务需求,可在方法里进行补偿策略。

测试消息确认机制

这里直接测试,不再进行项目搭建准备的工作,队列交换机的创建可参考个人主页里的其他文章。

准备一个交换机和一个队列

	@Bean("order_event_exchange") //直接注入名字,后边绑定队列和交换机直接使用注解更方便
	public Exchange oderEventExchange(){
	     //durable:是否持久化 autoDelete:是否自动删除
	     DirectExchange directExchange = new DirectExchange("order_event_exchange",true, false);
	     return directExchange;
	 }
	@Bean("order_release_queue")
	public Queue orderReleaseQueue() {
	     Queue queue = new Queue("order_release_queue", true, false, false);
	     return queue;
	 }
	 @Bean
	 public Binding orderReleaseOrderBinging(@Qualifier("order_release_queue") Queue queue,
	                                         @Qualifier("order_event_exchange") Exchange exchange) {
	     return BindingBuilder.bind(queue).to(exchange).with("order.release.order").noargs();
	 }    

分别测试两个阶段:生产者-broker、交换机-队列。

  • 生产者到broker:指定错误的队列或者交换机
    编写接口逻辑,这里故意把交换机指定错误
@PostMapping("/sendOrder")
    public String sentOrder(@RequestBody Order order){
        //CorrelationData:给消息设置一个唯一id
        rabbitTemplate.convertAndSend("aaaaa","order.release.order", order,new CorrelationData(order.getId()));
        return "ok";
    }

测试结果
在这里插入图片描述
因为找不到对应的交换机,所以投递消息到broker失败,触发消息确认回调方法。

  • 交换机到队列:指定错误的路由键
    编写接口,指定错误的runingkey
@PostMapping("/sendOrder")
    public String sentOrder(@RequestBody Order order){
        rabbitTemplate.convertAndSend("order_event_exchange","222222", order,new CorrelationData(order.getId()));
        return "ok";
    }

测试结果,收到返回消息
在这里插入图片描述

二、手动确认机制

消息消费确认方法

消息消费确认主要会用到三个方法:
basicAck()、basicNack()/basicReject();
basicAck是确认消息收到,另两个方法是拒绝消息。
basicNack()和basicReject()方法的区别就是basicNack可以批量拒绝消息,比如:由于某些原因造成队列里有其他未确认消息,使用basicNack设置是否批量拒绝参数为true,就会把本次及之前的所有未确认消息全部拒绝。
具体看代码,我们可以在消费端这样写:

public void handleStockLockedRelease(SkuUnlockInfoTo skuUnlockInfoTo, Message message, Channel channel) throws IOException {
        try {
            orderService.unLockStock(skuUnlockInfoTo);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            e.printStackTrace();
            //是否是被重试的消息
            if (message.getMessageProperties().isRedelivered()) {
                /*
                 * @param deliveryTag:消息的唯一标签,按消息入队顺序生成,比如:1、2、3
                 * @param multiple:是否开启消息批量拒绝
                 * @param requeue:是否将消息重新放入队列
                 */
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
                //TODO 防止异常死循环,最好放在数据库或记录一下,用定时任务定时重试
            } else {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),  true);
            }
        }
    }

利用try…catch即可针对异常来做出处理方法。如果想更精细一些,那么我们就可以利用消费者重试机制了,它可以设置重试次数,每次重试的间隔时间等。

三、消费者重试机制

说明

1、重试机制是基于本地的,跟rabbitmq队列没有任何关系,并不会将消息反复投递到队列,然后重复消费。
2、消费者端的代码中不能使用try{}catch(){}捕获异常,否则异常无法抛出,也就无法触发重试机制,非得捕获已知异常,也要在catch中重新抛出未知异常来触发重试机制。
3、消息重试次数用完之后,消息就需要被处理,这里rabbit提供了3个恢复器。
在这里插入图片描述
各恢复器含义:

RejectAndDontRequeueRecoverer:拒绝并且不会将消息重新发回队列;
RepublishMessageRecoverer:重新发布消息(自定义将消息转发到指定的其他交换机、队列);
ImmediateRequeueMessageRecoverer:立即重新返回原队列。

常用 RepublishMessageRecoverer;
默认 RejectAndDontRequeueRecoverer。
4、除了可以使用mq提供的恢复器来实现消息的自定义处理,也可以利用死信队列来达到目的。
接下来,我们分别利用mq恢复器和死信队列来实现消息的转发。

yml配置

spring:
  rabbitmq:
    host: 192.168.25.131
    port: 5672
    virtual-host: /
    #开启发送端确认,mq服务是否收到消息,收到消息触发confirmCallback方法回调
    publisher-confirm-type: correlated
    #开启发送端消息抵达队列的确认,消息是否从【交换机/其他】到达指定队列,触发returnCallback进行回调,详情看配置文件描述
    publisher-returns: true
    #只要抵达队列,以异步发送优先回调我们这个return confirm【跟publisher-returns是一组都设置成true】
    template:
      mandatory: true
    listener:
      simple:
        # 消息确认模式 manual:手动ack  auto:自动
        acknowledge-mode: auto
        # 消费者消息重试机制配置
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 3000 # 初识的失败等待时长为1秒
          multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3 # 最大重试次数
          stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
    username: admin
    password: 123456

RepublishMessageRecoverer实现转发

流程

需要再多定义一个交换机和队列,专门用来接收处理超出重试机制次数的消息,通过MessageRecoverer的实现类RepublishMessageRecoverer来设置。

配置

在上文正常的交换机(order_event_exchange)和队列(order_release_queue)的基础下,再多创建一套

	@Bean("error_event_exchange")
    public DirectExchange errorMessageExchange(){
        DirectExchange directExchange = new DirectExchange("error_event_exchange",true, false);
        return directExchange;
    }
    @Bean("error_queue")
    public Queue errorQueue(){
        Queue queue = new Queue("error_queue", true, false, false);
        return queue;
    }

    @Bean
    public Binding errorBinding(@Qualifier("error_queue") Queue queue,
                                @Qualifier("error_event_exchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("error").noargs();
    }
	/**
     * 设置消息重试机制完成之后,异常依然存在,消息应该到达哪里。
     */
    @Bean
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, "error_event_exchange", "error");
    }

测试

  • 正常消费端接口代码
@Service
@RabbitListener(queues = {"order_release_queue"})
public class OrderCloseListener {

    @RabbitHandler
    public void listener(Order order) {
        System.out.println("收到order信息");
        System.out.println(order);
        //模拟异常
        int a = 1 / 0;
    }
 }
  • 异常接收消费端代码
@Service
@RabbitListener(queues = {"error_queue"})
public class OrderErrorListener {

    @RabbitHandler
    public void listener(Order order) throws IOException {
        System.out.println("收到error-order信息");
        System.out.println(order);
    }
 }
  • 队列详情

在这里插入图片描述

  • 测试结果
    在这里插入图片描述
    3次重试之后,消息转发到error交换机、队列,然后被消费。

死信队列实现

说明及流程

  • 说明

死信队列就是普通的队列,换了一种叫法而已,当然,也要清楚什么情况下,消息才会被转发到死信队列:
1、过期的消息;2、超出队列长度的消息;3、被拒绝且不会被重新入队的消息。

  • 流程

这里,我们就要利用死信的第三个特点来实现消息自动转发效果,上文我们说过,消息重试机制的默认恢复器是RejectAndDontRequeueRecoverer,而这个恢复器的特点就是将消息拒绝并删除掉(不会重新入队)。所以,与其说用死信队列来实现,倒不如说用消息重试机制的默认恢复器+死信队列来实现消息的转发。

配置

首先注释掉前边配置的RepublishMessageRecoverer,否则会覆盖默认恢复器,另外,给正常队列order_release_queue设置死信交换机和队列,当然,这里的死信交换机和队列就是我们之前创建的error系列。
完整代码

@Configuration
public class MyMQConfig {

    /**
     *功能描述: 创建交换机
     * @author zhouwenjie
     * @date 2022/5/6 0:36
     * @param
     * @return org.springframework.amqp.core.Exchange
     */
    @Bean("order_event_exchange") //直接注入名字,后边绑定队列和交换机直接使用注解更方便
    public Exchange oderEventExchange(){
        //durable:是否持久化 autoDelete:是否自动删除
        DirectExchange directExchange = new DirectExchange("order_event_exchange",true, false);
        return directExchange;
    }

    /**
     * 功能描述: 【订单相关】声明正常收取队列
     *
     * @param
     * @return org.springframework.amqp.core.Queue
     * @author zhouwenjie
     */
    @Bean("order_release_queue")
    public Queue orderReleaseQueue() {
        Map<String, Object> arguments = new HashMap<>();
        //配置队列的死信应该发送给哪个交换机
        arguments.put("x-dead-letter-exchange", "error_event_exchange");
        //发送给交换机使用的路由key
        arguments.put("x-dead-letter-routing-key", "error");
        Queue queue = new Queue("order_release_queue", true, false, false,arguments);
        return queue;
    }

    @Bean
    public Binding orderReleaseOrderBinging(@Qualifier("order_release_queue") Queue queue,
                                            @Qualifier("order_event_exchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("order.release.order").noargs();
    }


    @Bean("error_event_exchange")
    public DirectExchange errorMessageExchange(){
        DirectExchange directExchange = new DirectExchange("error_event_exchange",true, false);
        return directExchange;
    }
    @Bean("error_queue")
    public Queue errorQueue(){
        Queue queue = new Queue("error_queue", true, false, false);
        return queue;
    }

    @Bean
    public Binding errorBinding(@Qualifier("error_queue") Queue queue,
                                @Qualifier("error_event_exchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("error").noargs();
    }
}

测试

基于之前的监听代码不动,接口也一样,另外,注意一下,重启项目之前一定要先删除原来的正常队列,让程序自动基于现在的配置重建,否则会报异常,如下图即可。
在这里插入图片描述

  • 测试结果
    因为此种方式控制台会输出一堆异常信息,正常的 /by zero 异常,所以分开截取。
    在这里插入图片描述
    在这里插入图片描述

注意事项

  • 重试机制和确认模式的搭配建议
    这里提前说明一下,如果使用了消费者重试机制(retry),那么消费端确认模式最好使用AUTO模式,程序正常执行完或者抛出异常才删除消息,这样才能保证在程序抛出异常的时候,消息才能正确被处理。
    在这里插入图片描述
    原因很简单,如果使用手动模式MANUAL,程序在执行途中就出现异常,就不会执行到最后一行手动确认消息的代码,消费端就没有机会用ack去响应MQ。因此 ,在MQ管理端就会一直存在一条消息没有被消费(实验了很多次,决定放弃这个模式),就如下图一样,永远无法执行到basicAck。
    在这里插入图片描述
  • 消息自定义序列化和确认机制的兼容
    这里说明一下我遇到的问题场景:
    因为rabbit默认使用jdk自带的序列化机制,所以我想换成Jackson2,所以就要重新定义RabbitTemplate和的bean来进行序列化和反序列化,如下图。
    在这里插入图片描述在这里插入图片描述
    但是这样之后,发现这个配置会覆盖rabbit的其他配置,比如下边的这些。
    在这里插入图片描述
    导致这些配置都不生效,后来经过一系列的操作,也是解决了这个问题,具体请参考这里:
    rabbitmq自定义消息序列化与反序列化

四、rabbitmq生产环境一些参数设置

参考这篇文章

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
您可以使用RabbitMQSpring Boot和WebSocket来实现前端可发送和接收消息的功能。下面是一个基本的实现步骤: 1. 首先,确保您的Spring Boot项目中已经添加了RabbitMQ和WebSocket的依赖。 2. 在Spring Boot应用程序中配置RabbitMQ,包括连接配置和队列配置。您可以使用`@Configuration`注解创建一个配置类,并使用`@Bean`注解创建一个`ConnectionFactory`和一个`RabbitTemplate`实例。 3. 创建一个消息接收器(Consumer)来监听RabbitMQ队列中的消息。您可以使用`@RabbitListener`注解将一个方法标记为消息接收器,并指定要监听的队列名称。 4. 在Spring Boot应用程序中配置WebSocket,包括处理器和拦截器等。您可以使用`@Configuration`注解创建一个配置类,并使用`@Bean`注解创建一个`WebSocketHandler`和一个`HandshakeInterceptor`实例。 5. 创建一个WebSocket处理器(Handler)来处理前端发送的消息。您可以实现`WebSocketHandler`接口,并重写相应的方法来处理连接、消息发送和关闭等事件。 6. 在WebSocket处理器中,您可以使用RabbitTemplate将接收到的消息发送到RabbitMQ队列中。您可以在处理器的`handleTextMessage()`方法中调用RabbitTemplate的相关方法来发送消息。 7. 在前端页面中,使用JavaScript或其他框架来建立WebSocket连接,并发送和接收消息。您可以使用WebSocket的API来发送和接收消息,并在接收到消息时更新页面内容。 通过以上步骤,您可以实现前端可发送和接收消息的功能。当前端发送消息时,WebSocket处理器会接收到消息并将其发送到RabbitMQ队列中。然后,消息接收器会监听该队列,并将消息发送给其他需要接收该消息的客户端。这样,前端页面就可以实现实时的消息发送和接收功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值