【十七】RabbitMQ基础篇(延迟队列和死信队列实战)

简介: 【十七】RabbitMQ基础篇(延迟队列和死信队列实战)


       本章将通过学习rabbitMQ基础中的延时队列和死信队列,然后写一个demo实现一个小例子,在商城购物时,先下单创建订单记录,然后可以选择进行立即支付或者不支付,若30秒后不支付,则删除订单。下面针对这个例子进行学习。


首先展示一下最终效果,并进行效果讲解,如下所示:

  • 正常购买流程

描述:点击购买,创建订单记录,在倒计时内支付成功的话,正常完成购买流程。

  • 未支付流程

描述:点击购买,创建订单记录,在倒计时内未支付成功的话,删除该笔订单。


       下面为了满足上述效果,进行实现。

一、分析例子

       为了满足上面的效果,可以通过很多方法实现,最简单的就是定时任务,创建一个定时任务,定时去请求数据,查看状态为未支付的订单,并删除。当然还可以通过redis或者其他办法,本章当然是通过RabbitMQ的方式实现,将通过延时队列和死信队列实现,逻辑关系如下:

所以需要创建如下几个东西:

  • 延时交换机
  • 延时队列
  • 延时队列绑定关系
  • 死信交换机
  • 死信队列
  • 死信队列绑定关系
  • 消费者端死信队列监听器
  • 生产者端创建订单接口(内含发送消息到延时队列)
  • 生产者端支付订单接口

分析完毕,下面开整。

建表语句

CREATE TABLE `dingdan` (
  `id` varchar(255) COLLATE utf8_bin NOT NULL,
  `name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `type` int DEFAULT NULL COMMENT '0:未支付,1:已支付,默认0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

二、编写前端代码

       上面测试效果是随便整的,过于简便,代码我直接贴出来:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>订单</title>
  </head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  
  <body>
    <div style="width: 100%;height: 300px;display: flex;justify-content: center;margin-top: 200px;">
      <div class="card" style="width: 15rem;">
        <img class="card-img-top" style="" src="./img/apple.jpg" alt="Card image cap">
        <div class="card-body">
          <h5 class="card-title">苹果售价:100¥</h5>
          <a onclick="fun1()" style="margin-top: 20px;margin-left: 70px;" class="btn btn-primary">购买</a>
        </div>
      </div>
    </div>
  </body>
  
  <script type="text/javascript">
    function fun1(){
      console.log("点击");
        
      $.ajax({
        url:"http://localhost:7778/dingdanController/insertDingdan",
        data:null,
        type:"post",
        dataType: "json",
        success: function(data) { 
          console.log("返回值:"+data.id);
          window.location.href="支付.html?id="+data.id
        }
      });
    }
  </script>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>支付</title>
  </head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  
  <body>
    <div style="width: 100px;height: 30px;text-align: center;line-height: 30px;margin-top: 100px;margin-left: 50%;background-color: aliceblue;">
      支付倒计时:
    </div>
    <div id="time" style="width: 100px;height: 30px;text-align: center;line-height: 30px;margin-top: 10px;margin-left: 50%;background-color: aliceblue;">
      
    </div>
    <button onclick="fun1()" style="margin-top: 10px;margin-left: 50%;" type="button" class="btn btn-primary">确定支付</button>
  </body>
  <script type="text/javascript">
    
    var id = getid('id');
    console.log(id);
    // alert(id);
    
    var i=15;
    $("#time").html(i);
    var time1 =setInterval(function(){
      if(i>0){
        i=i-1;
        $("#time").html(i);
      }else{
        clearInterval(time1);
      }
    },1000)
    
    var time2 = setInterval(function(){
      var i = $("#time").html();
      if(i==0){
        window.location.href="订单.html"
      }
    },1000)
    
    //支付
    function fun1(){
      alert("支付成功");
      clearInterval(time1);
      clearInterval(time2);
      $.ajax({
        url:"http://localhost:7778/dingdanController/pay",
        data:{id:id},
        type:"post",
        dataType: "json",
        success: function(data) { 
          
        }
      });
      
    }
    //取值
    function getid(names, urls) {
      urls = urls || window.location.href;
      urls && urls.indexOf("?") > -1 ? urls = urls
          .substring(urls.indexOf("?") + 1) : "";
      var reg = new RegExp("(^|&)" + names + "=([^&]*)(&|$)", "i");
      var r = urls ? urls.match(reg) : window.location.search.substr(1)
          .match(reg);
      if (r != null && r[2] != "")
        return unescape(r[2]);
      return null;
    };
  </script>
</html>

用的ajax和bootstrap。


三、整理模块

       下面开始本章的核心改造,首先补一下前面章节的坑,建立父子工程时,直接在父工程的maven进行打包会出异常,因为其中含有一个common公共模块,其他服务有使用commo模块的东西,为了避免编译整个父工程时再报错,在功能模块代码的pom文件,增加如下代码:

       这样,下次在父工程直接编译就不会再找不到各个模块对commom模块的依赖了。

       然后本次测试会涉及到数据库的操作,所以直接引入mybatis-plus,便于操作,关于mybatis-plus的操作,前面springboot整合篇有讲到。

此处再简单讲一下整合mybatis-plus简要步骤

  1. 导入依赖到父工程
  2. 修改provider模块和consumer模块业务模块的配置类yml文件 两个模块的yml配置文件记得都要修改。
  3. 关于报错,数据库版本问题可能导致依赖版本存在一些不一致,会出现一系列问题,根据报错百度一下即可解决

四、改造common公共模块

       接下来改造common模块,为了方便管理,每个模块公用的东西都放到了common子工程,上一章有将topic主题模式消息队列涉及到的常量放到common的RabbitMQConstant类中,但是本章为了方便看,就直接不将常量信息放到其中。

  1. 创建实体类   id使用mybatis-plus的UUID雪花算法自动生成。
  2. 本来想将mapper也放到其中,但是放到其中后,其他模块使用时会导致接口无法访问,问题还未解决

五、改造provider服务提供方

       还是展示一下改造后的provider服务的最终目录结构,如下:

框选部分为新增代码,下面开整。

  1. 新增mapper类
  2. 新增订单操作接口

简要描述:

       1、创建一个支付接口,一个创建订单接口。

       2、创建订单接口:创建订单记录,并发送消息(传订单id)到延时交换机,并将id返回前端方便前端调用支付接口。

       3、支付接口:根据订单id,改变订单已支付状态,避免被消息监听器处理。

六、改造consumer服务消费方

       接着改造consumer服务,最终目录结构如下:

框选处为新增代码。

  1. 新增mapper,和生产者服务一样,本来应该可以放到common,但是我还未解决问题,只能先这样处理。
  2. 新增延时队列和死信队列的配置类TopicDelayConfig。

简要描述:

       类似原来的主题模式的常规配置,只不过此处的延时队列创建时有所不同,需要先设置好各个参数再创建,注释有说明,此处为了方便操作,直接使用的路由模式,没有使用主题模式。      

  1. 创建延时交换机
  2. 创建死信交换机
  3. 创建延时队列,并设置延时时间以及,成为死信后进入哪一个交换机并设置路由键
  4. 创建死信队列
  5. 创建死信队列和死信交换机的绑定关系
  6. 创建延时队列和延时交换机的绑定关系

   3. 新增死信队列监听器        

简要描述:

       根据从队列接收到的消息处理具体的逻辑,根据订单id查询订单记录,若存在则判断是否已支付,若未支付则删除。

七、演示

       演示效果如本章开篇所展示的一致,此处就不展示了,感兴趣的朋友可以尝试一下,over。

——————————————————完毕——————————————————

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
1月前
|
消息中间件 存储 监控
RabbitMQ 队列之战:Classic 和 Quorum 的性能洞察
RabbitMQ 是一个功能强大的消息代理,用于分布式应用程序间的通信。它通过队列临时存储消息,支持异步通信和解耦。经典队列适合高吞吐量和低延迟场景,而仲裁队列则提供高可用性和容错能力,适用于关键任务系统。选择哪种队列取决于性能、持久性和容错性的需求。
127 6
|
1月前
|
消息中间件 数据采集 中间件
RabbitMQ的使用—实战
RabbitMQ的使用—实战
|
2月前
|
消息中间件 JSON Java
|
2月前
|
消息中间件
rabbitmq,&队列
rabbitmq,&队列
|
2月前
|
消息中间件 缓存 Java
RocketMQ的JAVA落地实战
RocketMQ作为一款高性能、高可靠、高实时、分布式特点的消息中间件,其核心作用主要体现在异步处理、削峰填谷以及系统解耦三个方面。
170 0
|
2月前
|
消息中间件 JSON Java
玩转RabbitMQ声明队列交换机、消息转换器
玩转RabbitMQ声明队列交换机、消息转换器
87 0
|
25天前
|
消息中间件 JSON Java
开发者如何使用轻量消息队列MNS
【10月更文挑战第19天】开发者如何使用轻量消息队列MNS
66 6
|
20天前
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
23天前
|
消息中间件
解决方案 | 云消息队列RabbitMQ实践获奖名单公布!
云消息队列RabbitMQ实践获奖名单公布!
|
1月前
|
消息中间件 安全 Java
云消息队列RabbitMQ实践解决方案评测
一文带你详细了解云消息队列RabbitMQ实践的解决方案优与劣
67 6