zoukankan      html  css  js  c++  java
  • 电商项目实战(架构八)——RabbitMQ实现延迟消息

    一、前言

      RabbitMQ是一个开源的消息队列,轻量级且易于部署,并支持多种消息协议。RabbitMQ可以部署在分布式和联合配置中,以满足高规模、高可用性的需求。本文整合RabbitMQ实现延迟消息的过程,以发送延迟消息取消超时订单为例.

    二、RabbitMQ的安装和使用

      1、安装Erlang,下载地址:http://erlang.org/download/otp_win_64_21.3.exe

      

      2、安装RabbitMQ,下载地址:https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe

      

       3、启动RabbitMQ,在cmd命令提示框中进入RabbitMQ安装目录下的sbin目录,执行命令:rabbitmq-plugins enable rabbitmq_management

      

       4、登录rabbitmq管理界面,访问地址:http://localhost:15672

      

       5、输入账号密码:guest     guest,登录成功后创建账号admin

      点击Admin

      

       6、创建一个新的虚拟host:/shop

      点击右边的Virtual Hosts

      

       7、给新创建的admin账号设置host

      点击新创建的账号admin,进入配置页面

      

       

      至此,RabbitMQ的安装和配置完成。

    三、RabbitMQ的消息模型

      

    标志  

    中文名   英文名  

    描述      

    p 生产者

    Producer  

    消息的发送者,可以将消息发送到交换机  
    C 消费者

    Consumer  

    消息的接收者,从队列中获取消息进行消费
    X 交换机 Exchange

    接收生产者发送到的消息,并根据路由键

    发送给指定队列

    Q

    队列

    Queue 存储从交换机发来的消息
    type 交换机类型

    type

    direct表示直接根据路由键(orange/black)

    发送消息

    四、项目整合RabbitMQ

      1、业务场景说明,RabbitMQ本次主要用于解决用户下单后,订单超时如何取消订单的问题

      · 用户进行下单操作(锁定商品库存,使用优惠券、积分等)

      · 生成订单,获取订单的id

      · 获取到设置的订单超时时间(假设设置60分钟不支付即取消订单)

      · 按订单超时时间发送一个延迟消息给RabbitMQ,让它在订单超时后触发取消订单

      · 如果用户没有支付,进行取消订单操作(释放锁定商品库存、返优惠券、返回积分一系列操作)

      2、在pom.xml中添加依赖

    <!--rabbitmq相关依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
            <!--lombok相关依赖-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>

      3、修改application.yml文件,在spring节点下添加RabbitMQ相关配置

    rabbitmq:
        host: localhost   # rabbitmq的连接地址
        port: 5672        # rabbitmq的连接端口号
        virtual-host: /shop     # rabbitmq的虚拟host
        username: admin     # rabbitmq的用户名
        password: 123456    # rabbitmq的密码
        publisher-confirms: true    # 如果对异步消息需要回调设置为true

      4、在com.zzb.test.admin.dto下添加消息队列的枚举配置类QueueEnum

    package com.zzb.test.admin.dto;
    
    import lombok.Getter;
    
    /**
     * 消息队列枚举配置
     * 用于延迟消息队列及处理取消订单消息队列的常量定义,包括交换机名称、队列名称、路由键名称等
     * Created by zzb on 2019/12/17 11:26
     */
    @Getter
    public enum QueueEnum {
        /**
         * 消息通知队列
         */
        QUEUE_ORDER_CANCEL("shop.order.direct", "shop.order.cancel", "shop.order.cancel"),
        /**
         * 消息通知ttl队列
         */
        QUEUE_TTL_ORDER_CANCEL("shop.order.direct.ttl", "shop.order.cancel.ttl", "shop.order.cancel.ttl");
        /**
         * 交换名称
         */
        private String exchange;
        /**
         * 队列名称
         */
        private String name;
        /**
         * 路由键
         */
        private String routeKey;
    
        QueueEnum(String exchange, String name, String routeKey){
            this.exchange = exchange;
            this.name = name;
            this.routeKey = routeKey;
        }
    }

      5、在com.zzb.test.admin.config包下添加rabbitmq的配置类RabbitMqConfig

    package com.zzb.test.admin.config;
    
    import com.zzb.test.admin.dto.QueueEnum;
    import org.springframework.amqp.core.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 消息队列配置
     * 用户配置交换机、队列及队列与交换机的绑定关系
     * Created by zzb on 2019/12/17 11:40
     */
    @Configuration
    public class RabbitMqConfig {
        /**
         * 订单消息实际消费队列所绑定的交换机
         * @return
         */
        @Bean
        DirectExchange orderDirect(){
            return (DirectExchange) ExchangeBuilder
                    .directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
                    .durable(true)
                    .build();
        }
    
        /**
         * 订单延迟队列所绑定的交换机
         * @return
         */
        @Bean
        DirectExchange orderTtlDirect(){
            return (DirectExchange) ExchangeBuilder
                    .directExchange(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange())
                    .durable(true)
                    .build();
        }
    
        /**
         * 订单实际消费队列
         * @return
         */
        @Bean
        public Queue orderQueue(){
            return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
        }
    
        /**
         * 订单延迟队列(死信队列)
         * @return
         */
        @Bean
        public Queue orderTtlQueue(){
            return QueueBuilder
                    .durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())
                    .withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
                    .withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())
                    .build();
        }
    
        /**
         * 将订单队列绑定到交换机
         * @return
         */
        @Bean
        Binding orderBinding(DirectExchange orderDirect, Queue orderQueue){
            return BindingBuilder
                    .bind(orderQueue)
                    .to(orderDirect)
                    .with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());
        }
    
        /**
         * 将订单延迟队列绑定到交换机
         * @param orderTtlDirect
         * @param orderTtlQueue
         * @return
         */
        @Bean
        Binding orderTtlBinding(DirectExchange orderTtlDirect, Queue orderTtlQueue){
            return BindingBuilder
                    .bind(orderTtlQueue)
                    .to(orderTtlDirect)
                    .with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());
        }
    }

       6、在com.zzb.test.admin.common包下添加延迟消息的发送类CancelOrderSender

    package com.zzb.test.admin.common;
    
    import com.zzb.test.admin.dto.QueueEnum;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.AmqpException;
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.amqp.core.Message;
    import org.springframework.amqp.core.MessagePostProcessor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    
    /**
     * 取消订单消息的发出者
     * 用于向订单延迟消息队列(shop.order.cancel.ttl)里发送消息
     * Created by zzb on 2019/12/17 14:30
     */
    @Component
    public class CancelOrderSender {
        private static Logger logger = LoggerFactory.getLogger(CancelOrderSender.class);
        @Autowired
        private AmqpTemplate amqpTemplate;
    
        public void sendMessage(Long orderId, long dalayTimes){
            //给延迟队列发送消息
            amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderId, new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    //给消息设置延迟毫秒值
                    message.getMessageProperties().setExpiration(String.valueOf(dalayTimes));
                    return message;
                }
            });
            logger.info((dalayTimes/1000)+"秒后发送取消订单的消息给订单:{}",orderId);
        }
    }

      7、在com.zzb.test.admin.common包下添加取消订单消息的接受类CancelOrderReceiver

    package com.zzb.test.admin.common;
    
    import com.zzb.test.admin.service.OmsOrderService;
    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.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * 取消订单消息的接受者
     * 用于从取消订单的队列里(shop.order.cancel)接收消息
     * Created by zzb on 2019/12/17 14:40
     */
    @Component
    @RabbitListener(queues = "shop.order.cancel")
    public class CancelOrderReceiver {
        private static Logger logger = LoggerFactory.getLogger(CancelOrderReceiver.class);
        @Autowired
        private OmsOrderService omsOrderService;
        @RabbitHandler
        public void handle(Long orderId){
            logger.info("接收队列取消订单的消息:{}", orderId);
            omsOrderService.cancelOrder(orderId);
        }
    
    }

      8、在service包下添加订单管理接口OmsOrderService

    package com.zzb.test.admin.service;
    
    import com.zzb.test.admin.common.CommonResult;
    import com.zzb.test.admin.dto.OrderParam;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * 订单管理接口
     * Created by zzb on 2019/12/17 14:47
     */
    public interface OmsOrderService {
        /**
         * 下单生成订单
         * @param orderParam
         * @return
         */
        @Transactional
        CommonResult generateOrder(OrderParam orderParam);
        /**
         * 取消单个超时订单
         * @param orderId
         */
        @Transactional
        void cancelOrder(Long orderId);
    }

      9、在impl包下添加其实现类OmsOrderServiceImpl

    package com.zzb.test.admin.service.impl;
    
    import com.zzb.test.admin.common.CancelOrderSender;
    import com.zzb.test.admin.common.CommonResult;
    import com.zzb.test.admin.dto.OrderParam;
    import com.zzb.test.admin.service.OmsOrderService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * 订单管理接口实现类
     * Created by zzb on 2019/12/17 14:49
     */
    @Service
    public class OmsOrderServiceImpl implements OmsOrderService {
        private static Logger logger = LoggerFactory.getLogger(OmsOrderServiceImpl.class);
        @Autowired
        private CancelOrderSender cancelOrderSender;
        @Override
        public CommonResult generateOrder(OrderParam orderParam) {
            // TODO: 2019/12/17 下单生成订单
            logger.info("下单成功,获取到订单id:{}", 1L);
            //设置延迟发送时间,测试设置为30秒
            long delayTimes = 30*1000;
            //发送延迟消息
            cancelOrderSender.sendMessage(1L, delayTimes);
            return CommonResult.success("下单成功");
        }
    
        @Override
        public void cancelOrder(Long orderId) {
            // TODO: 2019/12/17 取消单个超时订单
            logger.info("根据orderId取消超时订单:{}", orderId);
        }
    
    }

      10、在dto包下添加订单传入参数类OrderParam

    package com.zzb.test.admin.dto;
    
    import lombok.Getter;
    import lombok.Setter;
    
    /**
     * 生成订单时传入的参数
     * Created by zzb on 2019/12/17 14:57
     */
    @Getter
    @Setter
    public class OrderParam {
        //收货地址id
        private Long memeberAddressId;
        //优惠券id
        private Long couponId;
        //使用的积分
        private Integer useIntegration;
        //支付的方式
        private Integer payType;
    
    }

      11、在controller包下添加订单管理控制器OmsOrderController

    package com.zzb.test.admin.controller;
    
    import com.zzb.test.admin.common.CommonResult;
    import com.zzb.test.admin.dto.OrderParam;
    import com.zzb.test.admin.service.OmsOrderService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * 订单管理Controller
     * Created by zzb on 2019/12/17 15:10
     */
    @Controller
    @Api(tags = "OmsOrderController", description = "订单管理")
    public class OmsOrderController {
        @Autowired
        private OmsOrderService omsOrderService;
    
        @ApiOperation("下单生成订单")
        @RequestMapping(value = "/admin/oms/generateOrder", method = RequestMethod.POST)
        @ResponseBody
        public CommonResult generateOrder(@RequestBody OrderParam orderParam){
            return omsOrderService.generateOrder(orderParam);
        }
    }

    五、测试

      1、启动项目,在RabbitMQ管理界面查看自动生产的交换机及队列

      

       

      交换机及队列说明

    • mall.order.direct(取消订单消息队列所绑定的交换机):绑定的队列为mall.order.cancel,一旦有消息以mall.order.cancel为路由键发过来,会发送到此队列。

    • mall.order.direct.ttl(订单延迟消息队列所绑定的交换机):绑定的队列为mall.order.cancel.ttl,一旦有消息以mall.order.cancel.ttl为路由键发送过来,会转发到此队列,并在此队列保存一定时间,等到超时后会自动将消息发送到mall.order.cancel(取消订单消息消费队列)。

      2、访问swagger-ui.html测试页,登录,登录方法详见:https://www.cnblogs.com/zzb-yp/p/11899880.html

      登录成功后访问下单接口

      

      下单成功后发送消息

       

      30秒后自动触发取消订单方法

       

       项目github地址:https://github.com/18372561381/shoptest

      

      

  • 相关阅读:
    setprecision和setiosflags函数
    free和malloc的使用
    栈内存与堆内存
    一个教科书一般的“空指针”错误
    孙鑫教程第三章小测试程序
    向函数传递结构体信息
    指针(二)
    指针(一)
    cocos2d场景和UIViewController视图的切换
    UIScrollView 原理详解
  • 原文地址:https://www.cnblogs.com/zzb-yp/p/12050370.html
Copyright © 2011-2022 走看看