• RabbitMQ --- 直连交换机 【 同步操作,等到消费者处理完后返回处理结果 】


    1.前言

      RabbleMQ这种消息中间件,主要的功能是使用异步操作,来达到解耦的目的,比如,有系统a和系统b,

    系统a通过消息中间件通知系统b来做业务,那么系统a只需要把要做的事情【也就是消息】发给消息中间件后,

    消息中间件就会把消息转发给系统b,系统a不需要关心系统b是怎么完成业务的,也不需要关心业务完成的结果,

    这是就是异步操作。

      如果系统a希望获得系统b的处理结果,那么系统a使用消息中间件发送消息后需要原地等待,做阻塞操作,但是

    等待时长不能超过最大超时时间,可设置RabbleMQ自定义超时时间,这样还不如直接调用该业务呢,何必再加个消息消息中间件通知他来做?

      所以,一般不会做这样的同步阻塞操作,违背了消息中间件的开发初衷【提高吞吐量和系统业务的响应速度】,虽然

    不影响解耦度,但是这样的操作使得消息中间件变得不伦不类了。

      那么要问了,使用RabbleMQ消息中间件的具体好处是什么?

      第一,解耦,是非常明显的好处,通过消息中间件,系统a通知系统b做什么业务,不需要关心系统b是怎么实现业务的。
      第二,异步,非必要的业务,不需要特别关心结果的业务,可以写入消息中间件以异步方式操作,横向编程,加快响应速度。
      第三,削峰,并发量大的时候直接将所有数据怼到数据库,数据库会异常的,使用消息队列的特性,所有的操作会进行排队,
        消息也不会丢失,当消息被消费后才会销毁,比如秒杀系统就是这样实现的。

      那么,虽然同步阻塞操作偶很大的闭端,那么到底该怎么操作?

    这篇随笔 以 随笔 https://www.cnblogs.com/c2g5201314/p/13156932.html 为基础 ,修改部分代码实现演示

    2.操作

    (1)修改消息生产者的消息生产类的 rabbitTemplate 模板发送消息方法 ,同步则使用 convertSendAndReceive(),参数与异步非阻塞操作的一样。

     

     源码

    package com.example.rabbitmqproducer1004.rabbitmqFactory;
    
    
    import com.example.rabbitmqproducer1004.config.RabbitmqConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.connection.CorrelationData;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.UUID;
    
    /**
     * 消息生产类
     */
    @Component
    //实现接口
    public class SendMessage  {
    
    //需要设置回调方法,获取消费结果才需要实现 RabbitTemplate.ConfirmCallback 接口,
    //public class SendMessage implements RabbitTemplate.ConfirmCallback {
        Logger logger = LoggerFactory.getLogger(this.getClass());
    
    //======================================================================
    //    /**
    //     * 方法一:设置回调方法,获取消费结果,
    //     * <p>
    //     * 缺点是:必须手动配置RabbitTemplate模板 ,代码量大
    //     */
    //
    //    //存储 rabbitmq模板的临时变量
    //    private final RabbitTemplate rabbitTemplate;
    //
    //    /**
    //     * 构造注入rabbitmq模板,这样可以设置回调方法,获取消费结果,但是必须手动配置RabbitTemplate模板
    //     */
    //    @Autowired
    //    public SendMessage(RabbitTemplate rabbitTemplate) {
    //        this.rabbitTemplate = rabbitTemplate;
    //        //设置确认回调的方法,参数类型为ConfirmCallback
    //        this.rabbitTemplate.setConfirmCallback(this);
    //    }
    //
    //    /**
    //     * 回调方法,获取消费结果
    //     *
    //     * @param correlationData 关联数据
    //     * @param b               消息是否被消费成功,成功为true ,失败为false
    //     * @param s               原因 ,消费成功则返回null,否则返回失败原因
    //     */
    //    @Override
    //    public void confirm(CorrelationData correlationData, boolean b, String s) {
    //        logger.warn("回调的连接数据:" + correlationData);
    //        if (correlationData != null) {
    //            //CorrelationData [id=1bcab025-2b4c-4f74-a22d-41007e30f551]
    //            logger.warn("获取correlationData的id值:" + correlationData.getId());
    //        }
    //        //1bcab025-2b4c-4f74-a22d-41007e30f551
    //        if (b) {
    //            logger.warn("回调结果:消息消费成功");
    //        } else {
    //            logger.warn("回调结果:失败。原因:" + s);
    //        }
    //    }
    
    //========================================================================
    //    /**
    //     * 方法二 :不需要获取获取消费结果,只需要发送即可
    //     *
    //     * 优点:自动装配,代码量少
    //     */
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
    //========================================================================
    
        /**
         * 发送消息
         * <p>
         * 参数是消息内容
         */
        public void send(String message) {
            logger.warn("发送消息,内容:" + message);
            /**
             * 方法一:异步操作,不等待消费者端返回处理结果,设置在回调操作的关联数据,用于识别是哪一条消息和确认是否执行成功
             */
    ////        实例关联数据对象,使用UUID随机数 作为 回调id
    //        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    ////        发送消息 ,参数分别是 : 指定的交换机名字 、指定的路由关键字、消息字符串、关联数据
    //        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANG_1,RabbitmqConfig.ROUTINGKEY_1,message,correlationData);
            /**
             * 方法二:异步操作,不等待消费者端返回处理结果,且在消息回调操作的关联数据为null,如果不做回调操作,则建议这样使用
             */
    //        //发送消息 ,参数分别是 : 指定的交换机名字 、指定的路由关键字、消息字符串
    //        rabbitTemplate.convertAndSend(RabbitmqConfig.EXCHANG_1, RabbitmqConfig.ROUTINGKEY_1, message);
            /**
             * 方法三:同步操作,等待消费者端返回处理结果
             */
            Object dd = rabbitTemplate.convertSendAndReceive(RabbitmqConfig.EXCHANG_1, RabbitmqConfig.ROUTINGKEY_1, message);
            logger.warn("你大爷的,有么有延迟?是不是同步阻塞?");
            logger.warn("结果是什么???==" + dd);
    
    
        }
    }
    View Code

     (2)修改消息消费者的监听方法 ,可设置任意类型返回值,但是在生产者端需要解析,我这里使用 字符串,休眠3秒

     源码

    package com.example.rabbitmqconsumer1002.rabbitmqListener;
    
    import com.example.rabbitmqconsumer1002.config.RabbitConfig;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.annotation.RabbitHandler;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * 消息监听类--发短信
     */
    //注册bean
    @Component
    //设置需要监听的消息队列
    @RabbitListener(queues = RabbitConfig.QUEUE_1)
    public class SendMessageListener {
        Logger logger = LoggerFactory.getLogger(getClass());
    
    
    
        //消息事件处理--有返回结果
        @RabbitHandler
        public String sendMessage(String msg) throws InterruptedException {
            logger.warn("我是端口1002的消费者,收到信息:" + msg);
            logger.warn("休眠3秒");
            Thread.sleep(3000);
            logger.warn("休眠结束");
            return "发送成功,是同步阻塞的么?";
        }
    }
    View Code

    (3)启动工程

    访问网址     http://localhost:1004/mq?msg=你大爷,帮我发短信3999

    查看消费者端控制台打印

     查看生产者端控制台打印

     

     生产者端等待了3秒后才收到结果

    3.如果换成10秒会怎么样?

     修改消息消费者的监听方法

     启动工程后 访问 网址  http://localhost:1004/mq?msg=你大爷,帮我发短信3999

    查看消费者端控制台打印

     

      查看生产者端控制台打印

     打印控制台源码

    2020-06-19 02:40:17.100 WARN  http-nio-1004-exec-4 | com.example.rabbitmqproducer1004.rabbitmqFactory.SendMessage | 发送消息,内容:你大爷,帮我发短信3999
    2020-06-19 02:40:17.111 INFO  http-nio-1004-exec-4 | org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler | Initializing ExecutorService
    2020-06-19 02:40:17.117 INFO  http-nio-1004-exec-4 | org.springframework.amqp.rabbit.listener.DirectReplyToMessageListenerContainer | Container initialized for queues: [amq.rabbitmq.reply-to]
    2020-06-19 02:40:17.128 INFO  http-nio-1004-exec-4 | org.springframework.amqp.rabbit.listener.DirectReplyToMessageListenerContainer | SimpleConsumer [queue=amq.rabbitmq.reply-to, consumerTag=amq.ctag-pPnAaXXfnkytocGRd7ES5Q identity=7ba94c9e] started
    2020-06-19 02:40:22.137 WARN  http-nio-1004-exec-4 | com.example.rabbitmqproducer1004.rabbitmqFactory.SendMessage | 你大爷的,有么有延迟?是不是同步阻塞?
    2020-06-19 02:40:22.137 WARN  http-nio-1004-exec-4 | com.example.rabbitmqproducer1004.rabbitmqFactory.SendMessage | 结果是什么???==null
    2020-06-19 02:40:27.158 WARN  pool-1-thread-4 | org.springframework.amqp.rabbit.core.RabbitTemplate | Reply received after timeout for 1
    2020-06-19 02:40:27.163 WARN  pool-1-thread-4 | org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler | Execution of Rabbit message listener failed.
    org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:1651) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1555) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1478) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1466) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1461) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1410) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer$SimpleConsumer.callExecuteListener(DirectMessageListenerContainer.java:996) [spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer$SimpleConsumer.handleDelivery(DirectMessageListenerContainer.java:956) [spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at com.rabbitmq.client.impl.ConsumerDispatcher$5.run(ConsumerDispatcher.java:149) [amqp-client-5.4.3.jar:5.4.3]
        at com.rabbitmq.client.impl.ConsumerWorkService$WorkPoolRunnable.run(ConsumerWorkService.java:104) [amqp-client-5.4.3.jar:5.4.3]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_221]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_221]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_221]
    Caused by: org.springframework.amqp.AmqpRejectAndDontRequeueException: Reply received after timeout
        at org.springframework.amqp.rabbit.core.RabbitTemplate.onMessage(RabbitTemplate.java:2535) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.DirectReplyToMessageListenerContainer.lambda$setMessageListener$1(DirectReplyToMessageListenerContainer.java:115) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1552) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        ... 11 common frames omitted
    2020-06-19 02:40:27.164 ERROR pool-1-thread-4 | org.springframework.amqp.rabbit.listener.DirectReplyToMessageListenerContainer | Failed to invoke listener
    org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:1651) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1555) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1478) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1466) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1461) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1410) ~[spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer$SimpleConsumer.callExecuteListener(DirectMessageListenerContainer.java:996) [spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer$SimpleConsumer.handleDelivery(DirectMessageListenerContainer.java:956) [spring-rabbit-2.1.7.RELEASE.jar:2.1.7.RELEASE]
        at com.rabbitmq.client.impl.ConsumerDispatcher$5.run(ConsumerDispatcher.java:149) [amqp-client-5.4.3.jar:5.4.3]
        at com.rabbitmq.client.impl.ConsumerWorkService$WorkPoolRunnable.run(ConsumerWorkService.java:104) [amqp-client-5.4.3.jar:5.4.3]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_221]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_221]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_221]
    Caused by: org.springframework.amqp.AmqpRejectAndDontRequeueException: Reply received after timeout
    View Code

     抛出了监听异常,显然是等待超时了,然后继续执行执行下面的操作,返回结果以null处理

    4.分析与思考

    如果等待10分钟才能运行完这个业务,使用同步阻塞操作,岂不是需要等10分钟?那还要消息中间件有什么用?

    因此,一般是使用异步非阻塞操作消息中间件的,因此,会尽可能的把一些不需要返回结果的操作用消息中间件转发完成。

    对于失败和抛出异常的操作,可以制作一个死信队列,将这些无法完成的消息重写放入死信队列,让专门的系统监听进行处理,一般这样的业务很少出现,基本上是可以成功完成的,

    对那些重要的,必须获取处理结果的业务,直接调用实现就好了,因为这样是默认同步阻塞操作的,不需要再转一趟消息中间件啦。

  • 相关阅读:
    对silverlight布局进行控制,使其居中显示,适用于不同的分辨率
    图(邻接表链表和边表)
    LINUX下GCC编译sqrt函数问题
    图(邻接矩阵)
    表达式树
    赫夫曼树
    N的阶乘中末尾有几个0
    走迷宫
    HDU1863畅通工程(最小生成树 Kruskal)
    KMP算法
  • 原文地址:https://www.cnblogs.com/c2g5201314/p/13158901.html
Copyright © 2020-2023  润新知