电商项目实战(架构八)——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

  

  

原文地址:https://www.cnblogs.com/zzb-yp/p/12050370.html

时间: 2024-10-09 10:07:42

电商项目实战(架构八)——RabbitMQ实现延迟消息的相关文章

java架构师课程、性能调优、高并发、tomcat负载均衡、大型电商项目实战、高可用、高可扩展、数据库架构设计、Solr集群与应用、分布式实战、主从复制、高可用集群、大数据

15套Java架构师详情 * { font-family: "Microsoft YaHei" !important } h1 { background-color: #006; color: #FF0 } 15套java架构师.集群.高可用.高可扩展.高性能.高并发.性能优化.Spring boot.Redis.ActiveMQ.Nginx.Mycat.Netty.Jvm大型分布式项目实战视频教程 视频课程包含: 高级Java架构师包含:Spring boot.Spring  clo

16套java架构师,高并发,高可用,高性能,集群,大型分布式电商项目实战视频教程

16套Java架构师,集群,高可用,高可扩展,高性能,高并发,性能优化,设计模式,数据结构,虚拟机,微服务架构,日志分析,工作流,Jvm,Dubbo ,Spring boot,Spring cloud, Redis,ActiveMQ,Nginx,Mycat,Netty,Jvm,Mecached,Nosql,Spring,大型分布式项目实战视频教程 视频课程包含: 高级Java架构师包含:架构师,高并发,分布式,集群,高可用,高可扩展,高性能,设计模式,数据结构算法,虚拟机,微服务架构,日志分析,

Java企业级电商项目实战 Tomcat集群与Redis分布式

本套课程包含:java电商项目实战课程 Tomcat集群视频教程 Redis项目实战课程课程目录1-1 课程导学1-2 大型Java项目架构演进解析1-3 一期课程与问答服务回顾1-4 一期项目结构和代码回顾1-5 课程使用系统及技术版本介绍(一期+二期)1-6 二期项目初始化第2章 Lombok框架集成及原理解析本章会对Lombok框架进行介绍,同时会讲解Lombok的原理.并手把手领着小伙伴们实战,引入Lombok以及IDE安装Lombok插件.然后会带着大家实战Coding,讲解@Data

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

在如今的SpringBoot微服务项目中,前后端分离已成为业界标准使用方式,通过使用nginx等代理方式有效的进行解耦,并且前后端分离会为以后的大型分布式架构.弹性计算架构.微服务架构.多端化服务(多个客户展现端,例如:web端,安卓app,IOSapp,微信小程序等)打下坚实的基础.这个步骤是系统架构从猿进化成人的必经之路. image 上图是简单的分布式微服务开发及前后端分离的示意图.展现层也就是所谓的前端(客户可直观看到的),比如电商项目前端包含:app(安卓和IOS).微信小程序.PC商

Java从零到企业级电商项目实战

Java从零到企业级电商项目实战网盘地址:https://pan.baidu.com/s/1Ms8tin2fhe9TH_6VOmP-7g 密码:xhaz备用地址(腾讯微云):https://share.weiyun.com/5bpQSby 密码:38pnwv 六大课程亮点 让你拥有自己的在线电商项目学习成本低前后端彻底分离,按需学习前端和后端,让你更专注自己需要的技术 上手速度快手把手,由浅入深,步步为营,新手上手速度快 经验技巧多各种踩坑分享,各种贴心插件,各种开发技巧,倍增开发效率与准确性

React Native电商项目实战混合APP开发 React Native实战 混合APP实战开发

React Native  和 angular+ionic 是目前网络上最火的混合APP开发语言,其功能强大能够开发出安卓和IOS程序! ------------------课程目录------------------ <React Native电商项目实战>├<01React Native初体验>│  ├01-React Native简介.mp4│  ├02-React Native环境搭建.mp4│  ├03-React Native初体验及其它环境搭建.mp4│  └04-R

SpringBoot电商项目实战 — Zookeeper的分布式锁实现

上一篇演示了基于Redis的Redisson分布式锁实现,那今天我要再来说说基于Zookeeper的分布式现实. Zookeeper分布式锁实现 要用Zookeeper实现分布式锁,我就不得不说说zookeeper的数据存储.首先zookeeper的核心保存结构是一个DataTree数据结构,其实内部是一个Map<String, DataNode> nodes的数据结构,其中key是path,DataNode才是真正保存数据的核心数据结构,DataNode核心字段包括byte data[]用于

SpringBoot电商项目实战 — Redis实现分布式锁

最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeeper相关内容的也只有dubbo注册地址了.因为Zookeeper在项目中,我们不需要做任何配置和代码,只需要在服务器上安装一个Zookeeper应用即可. 包括对Zookeeper的依赖,我们在SpringBoot项目中只需要依赖Dubbo就ok了.在本次系列实战中,我是本着少说多动手的原则,如果有些

SpringBoot电商项目实战 — ElasticSearch接入实现

如今在一些中大型网站中,搜索引擎已是必不可少的内容了.首先我们看看搜索引擎到底是什么呢?搜索引擎,就是根据用户需求与一定算法,运用特定策略从互联网检索出制定信息反馈给用户的一门检索技术.搜索引擎依托于多种技术,如网络爬虫技术.检索排序技术.网页处理技术.大数据处理技术.自然语言处理技术等,为信息检索用户提供快速.高相关性的信息服务.搜索引擎技术的核心模块一般包括爬虫.索引.检索和排序等,同时可添加其他一系列辅助模块,以为用户创造更好的网络使用环境. image 基于Java的搜索引擎框架,目前市

web前端Vue+Django&#160;rest&#160;framework&#160;框架&#160;生鲜电商项目实战视频教程 学习

web前端Vue+Django rest framework 框架 生鲜电商项目实战视频教程 学习 1.drf前期准备 1.django-rest-framework官方文档 https://www.django-rest-framework.org/ #直接百度找到的djangorestframework的官网是打不开的 2.安装依赖包 如图所示,django restframework的依赖模块,除了coreapi和django-guardian,已经在前面安装过了. 打开终端,执行安装命令