RabbitMQ实现延迟消息居然如此简单,整个插件就完事了!

转载:https://juejin.im/post/6844904191245156360

SpringBoot实战电商项目mall(35k+star)地址:github.com/macrozheng/…

摘要

RabbitMQ实现延迟消息的方式有两种,一种是使用死信队列实现,另一种是使用延迟插件实现。死信队列实现我们以前曾经讲过,具体参考《mall整合RabbitMQ实现延迟消息》,这次我们讲个更简单的,使用延迟插件实现。

学前准备

学习本文需要对RabbitMQ有所了解,还不了解的朋友可以看下:《花了3天总结的RabbitMQ实用技巧,有点东西!》

插件安装

首先我们需要下载并安装RabbitMQ的延迟插件。

  • 去RabbitMQ的官网下载插件,插件地址:www.rabbitmq.com/community-p…

  • 直接搜索rabbitmq_delayed_message_exchange即可找到我们需要下载的插件,下载和RabbitMQ配套的版本,不要弄错;

  • 将插件文件复制到RabbitMQ安装目录的plugins目录下;

  • 进入RabbitMQ安装目录的sbin目录下,使用如下命令启用延迟插件;
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
复制代码
  • 启用插件成功后就可以看到如下信息,之后重新启动RabbitMQ服务即可。

实现延迟消息

接下来我们需要在SpringBoot中实现延迟消息功能,这次依然沿用商品下单的场景。比如说有个用户下单了,他60分钟不支付订单,订单就会被取消,这就是一个典型的延迟消息使用场景。

  • 首先我们需要在pom.xml文件中添加AMQP相关依赖;
<!--消息队列相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
复制代码
  • 之后在application.yml添加RabbitMQ的相关配置;
spring:
  rabbitmq:
    host: localhost # rabbitmq的连接地址
    port: 5672 # rabbitmq的连接端口号
    virtual-host: /mall # rabbitmq的虚拟host
    username: mall # rabbitmq的用户名
    password: mall # rabbitmq的密码
    publisher-confirms: true #如果对异步消息需要回调必须设置为true
复制代码
  • 接下来创建RabbitMQ的Java配置,主要用于配置交换机、队列和绑定关系;
/**
 * 消息队列配置
 * Created by macro on 2018/9/14.
 */
@Configuration
public class RabbitMqConfig {
<span class="hljs-comment">/**
 * 订单延迟插件消息队列所绑定的交换机
 */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function">CustomExchange  <span class="hljs-title">orderPluginDirect</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">//创建一个自定义交换机,可以发送延迟消息</span>
    Map&lt;String, Object&gt; args = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
    args.put(<span class="hljs-string">"x-delayed-type"</span>, <span class="hljs-string">"direct"</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CustomExchange(QueueEnum.QUEUE_ORDER_PLUGIN_CANCEL.getExchange(), <span class="hljs-string">"x-delayed-message"</span>,<span class="hljs-keyword">true</span>, <span class="hljs-keyword">false</span>,args);
}

<span class="hljs-comment">/**
 * 订单延迟插件队列
 */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Queue <span class="hljs-title">orderPluginQueue</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Queue(QueueEnum.QUEUE_ORDER_PLUGIN_CANCEL.getName());
}

<span class="hljs-comment">/**
 * 将订单延迟插件队列绑定到交换机
 */</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Binding <span class="hljs-title">orderPluginBinding</span><span class="hljs-params">(CustomExchange orderPluginDirect,Queue orderPluginQueue)</span> </span>{
    <span class="hljs-keyword">return</span> BindingBuilder
            .bind(orderPluginQueue)
            .to(orderPluginDirect)
            .with(QueueEnum.QUEUE_ORDER_PLUGIN_CANCEL.getRouteKey())
            .noargs();
}

}
复制代码

    • 创建一个取消订单消息的发出者,通过给消息设置x-delay头来设置消息从交换机发送到队列的延迟时间;
    • /**
       * 取消订单消息的发出者
       * Created by macro on 2018/9/14.
       */
      @Component
      public class CancelOrderSender {
          private static Logger LOGGER =LoggerFactory.getLogger(CancelOrderSender.class);
          @Autowired
          private AmqpTemplate amqpTemplate;
      
      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">sendMessage</span><span class="hljs-params">(Long orderId,<span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> delayTimes)</span></span>{
          <span class="hljs-comment">//给延迟队列发送消息</span>
          amqpTemplate.convertAndSend(QueueEnum.QUEUE_ORDER_PLUGIN_CANCEL.getExchange(), QueueEnum.QUEUE_ORDER_PLUGIN_CANCEL.getRouteKey(), orderId, <span class="hljs-keyword">new</span> MessagePostProcessor() {
              <span class="hljs-meta">@Override</span>
              <span class="hljs-function"><span class="hljs-keyword">public</span> Message <span class="hljs-title">postProcessMessage</span><span class="hljs-params">(Message message)</span> <span class="hljs-keyword">throws</span> AmqpException </span>{
                  <span class="hljs-comment">//给消息设置延迟毫秒值</span>
                  message.getMessageProperties().setHeader(<span class="hljs-string">"x-delay"</span>,delayTimes);
                  <span class="hljs-keyword">return</span> message;
              }
          });
          LOGGER.info(<span class="hljs-string">"send delay message orderId:{}"</span>,orderId);
      }
      

      }
      复制代码

      • 创建一个取消订单消息的接收者,用于处理订单延迟插件队列中的消息。
      • /**
         * 取消订单消息的处理者
         * Created by macro on 2018/9/14.
         */
        @Component
        @RabbitListener(queues = "mall.order.cancel.plugin")
        public class CancelOrderReceiver {
            private static Logger LOGGER =LoggerFactory.getLogger(CancelOrderReceiver.class);
            @Autowired
            private OmsPortalOrderService portalOrderService;
            @RabbitHandler
            public void handle(Long orderId){
                LOGGER.info("receive delay message orderId:{}",orderId);
                portalOrderService.cancelOrder(orderId);
            }
        }
        复制代码
        • 然后在我们的订单业务实现类中添加如下逻辑,当下单成功之前,往消息队列中发送一个取消订单的延迟消息,这样如果订单没有被支付的话,就能取消订单了;
        /**
         * 前台订单管理Service
         * Created by macro on 2018/8/30.
         */
        @Service
        public class OmsPortalOrderServiceImpl implements OmsPortalOrderService {
            private static Logger LOGGER = LoggerFactory.getLogger(OmsPortalOrderServiceImpl.class);
            @Autowired
            private CancelOrderSender cancelOrderSender;
        
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> CommonResult <span class="hljs-title">generateOrder</span><span class="hljs-params">(OrderParam orderParam)</span> </span>{
            <span class="hljs-comment">//todo 执行一系类下单操作,具体参考mall项目</span>
            LOGGER.info(<span class="hljs-string">"process generateOrder"</span>);
            <span class="hljs-comment">//下单完成后开启一个延迟消息,用于当用户没有付款时取消订单(orderId应该在下单后生成)</span>
            sendDelayMessageCancelOrder(<span class="hljs-number">11L</span>);
            <span class="hljs-keyword">return</span> CommonResult.success(<span class="hljs-keyword">null</span>, <span class="hljs-string">"下单成功"</span>);
        }
        
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">cancelOrder</span><span class="hljs-params">(Long orderId)</span> </span>{
            <span class="hljs-comment">//todo 执行一系类取消订单操作,具体参考mall项目</span>
            LOGGER.info(<span class="hljs-string">"process cancelOrder orderId:{}"</span>,orderId);
        }
        
        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">sendDelayMessageCancelOrder</span><span class="hljs-params">(Long orderId)</span> </span>{
            <span class="hljs-comment">//获取订单超时时间,假设为60分钟(测试用的30秒)</span>
            <span class="hljs-keyword">long</span> delayTimes = <span class="hljs-number">30</span> * <span class="hljs-number">1000</span>;
            <span class="hljs-comment">//发送延迟消息</span>
            cancelOrderSender.sendMessage(orderId, delayTimes);
        }
        

        }
        复制代码

        • 启动项目后,在Swagger中调用下单接口;
          • 调用完成后查看控制台日志可以发现,从消息发送和消息接收处理正好相差了30s,我们设置的延迟时间。
          2020-06-08 13:46:01.474  INFO 1644 --- [nio-8080-exec-1] c.m.m.t.s.i.OmsPortalOrderServiceImpl    : process generateOrder
          2020-06-08 13:46:01.482  INFO 1644 --- [nio-8080-exec-1] c.m.m.tiny.component.CancelOrderSender   : send delay message orderId:11
          2020-06-08 13:46:31.517  INFO 1644 --- [cTaskExecutor-4] c.m.m.t.component.CancelOrderReceiver    : receive delay message orderId:11
          2020-06-08 13:46:31.520  INFO 1644 --- [cTaskExecutor-4] c.m.m.t.s.i.OmsPortalOrderServiceImpl    : process cancelOrder orderId:11
          复制代码

          两种实现方式对比

          我们之前使用过死信队列的方式,这里我们把两种方式做个对比,先来聊下这两种方式的实现原理。

          死信队列

          死信队列是这样一个队列,如果消息发送到该队列并超过了设置的时间,就会被转发到设置好的处理超时消息的队列当中去,利用该特性可以实现延迟消息。

          延迟插件

          通过安装插件,自定义交换机,让交换机拥有延迟发送消息的能力,从而实现延迟消息。

          结论

          由于死信队列方式需要创建两个交换机(死信队列交换机+处理队列交换机)、两个队列(死信队列+处理队列),而延迟插件方式只需创建一个交换机和一个队列,所以后者使用起来更简单。

          项目源码地址

          github.com/macrozheng/…

          公众号

          mall项目全套学习教程连载中,关注公众号第一时间获取。

          公众号图片

        评论
        添加红包

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

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

        抵扣说明:

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

        余额充值