电商项目实战(架构八)——RabbitMQ实现延迟消息

Posted zzb-yp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了电商项目实战(架构八)——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

 

  

 

  

以上是关于电商项目实战(架构八)——RabbitMQ实现延迟消息的主要内容,如果未能解决你的问题,请参考以下文章

电商项目实战(架构七)——Mongodb实现文档操作

RabbitMQ实现延时队列

RabbitMQ 延迟任务(限时订单) 原理 以及代码 实战

SpringBoot电商项目实战 — 前后端分离后的优雅部署及Nginx部署实现

「MQ实战」RabbitMQ 延迟队列,消息延迟推送

RabbitMQ---延迟队列,整合springboot