谷粒商城-订单服务

这篇具有很好参考价值的文章主要介绍了谷粒商城-订单服务。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

商城业务-订单服务-RabbitMQ延时队列

商城业务-订单服务-延时队列定时关单模拟

商城业务-订单服务-创建业务交换机&队列

商城业务-订单服务-监听库存解锁

商城业务-订单服务-库存解锁逻辑

商城业务-订单服务-库存自动解锁完成

商城业务-订单服务-测试库存自动解锁

商城业务-订单服务-定时关单完成

商城业务-订单服务-消息丢失、积压、重复等解决方案


商城业务-订单服务-RabbitMQ延时队列

使用消息队列的目的是:保证数据的最终一致性

谷粒商城-订单服务

采用定时任务的方式:每隔一段时间进行全表的扫描,会消耗系统内存和增加数据库的压力,最致命的是存在较大的时间误差

假如:10:00定时任务开始执行,则10:01有用户下订单但未支付,10:30的时候定时任务再次执行,这个订单还差1分钟才能进行关单操作,因此,下一次扫描到它要等到11:00,存在着29分钟的误差时间。

谷粒商城-订单服务

采用消息队列可以完美的解决定时任务所带来的缺陷 

假如:10:00下订单,再下订单之前先给消息队列发送一条下单消息,等30分钟自动发送关闭订单消息,监听服务收到消息,去查看此订单是否完成支付,若未完成支付则关闭订单。误差也就一两分钟。对于解锁库存也是同理。

谷粒商城-订单服务

设置消息的过期时间: 在过期时间内都没有被消费则此消息将会被丢弃并称之为死信

设置队列的过期时间:在此过期时间内都没有队列被客户端连接则队列里的所有消息都被成为死信

谷粒商城-订单服务

死信路由: 消息过期未被消费的,则消息会被交给一个指定的路由器,这个路由器由于只接收死信所以被成为死信路由

谷粒商城-订单服务

RabbitMQ实现延时队列的原理:通过设置队列的过期时间使消息都变成死信,此队列是不能被任何服务监听的,当消息过期时,通过死信路由将死信路由给指定队列,指定队列只接收死信也就是延时消息,服务器专门监听指定队列从而达到定时任务的效果。

实现1:给队列设置过期时间,推荐使用

谷粒商城-订单服务

实现方式2:给消息设置过期时间,不推荐使用

不推荐使用的原因是:RabbitMQ采用的是懒检查,假如第一个消息设置的是5分钟过期,第二个消息设置的是2分钟过期,第三个消息设置的是30s过期,RabbitMQ过来一看消息5分钟后才过期,那么5分钟之后才会来将消息路由并不会关注后面消息的过期时间。

商城业务-订单服务-延时队列定时关单模拟

按照下图逻辑,模拟下单成功1分钟后,收到关闭订单的消息

谷粒商城-订单服务

谷粒商城-订单服务

编写队列、交换机,绑定关系 

容器中的 Binding、Queue、Exchange 都会自动创建(RabbitMQ没有的情况)

RabbitMQ只要有,@Bean声明的属性发生变化也不会覆盖

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MyMQConfig {

    @Bean
    public Queue orderDelayQueue(){
        Map<String,Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange","order-event-exchange");
        arguments.put("x-dead-letter-routing-key","order.release.order");
        arguments.put("x-message-ttl",60000);
        // String name, boolean durable, boolean exclusive, boolean autoDelete,
        //			@Nullable Map<String, Object> arguments
        Queue queue = new Queue("order.delay.queue", true, false, false, arguments);
        return queue;
    }


    @Bean
    public Queue orderReleaseOrderQueue(){
        Queue queue = new Queue("order.release.order.queue", true, false, false);
        return queue;
    }

    @Bean
    public Exchange orderEventExchange(){
        // String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
        TopicExchange exchange = new TopicExchange("order-event-exchange", true, false);
        return exchange;
    }

    @Bean
    public Binding orderCreateOrderBinding(){
        // String destination, DestinationType destinationType, String exchange, String routingKey,
        //			@Nullable Map<String, Object> arguments
        return new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE, "order-event-exchange",
                "order.create.order",
                null);
    }

    @Bean
    public Binding orderReleaseOrderBinding(){
        return new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE, "order-event-exchange",
                "order.release.order",
                null);
    }
}

监听关单事件

谷粒商城-订单服务

模拟订单完成 

谷粒商城-订单服务

商城业务-订单服务-创建业务交换机&队列

解锁库存的实现:

①库存服务导入RabbitMQ的依赖

  <!--  RabbitMQ的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

② RabbitMQ的配置

spring:
  rabbitmq:
    host: 192.168.56.22
    virtual-host: /

 配置RabbitMQ的序列化机制

import org.springframework.amqp.support.converter.AbstractMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRabbitMQConfig {
    
    @Bean
    public AbstractMessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}

④ 开启RabbitMQ

谷粒商城-订单服务

⑤ 按照下图创建交换机、队列、绑定关系

谷粒商城-订单服务

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.converter.AbstractMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MyRabbitMQConfig {

    @RabbitListener(queues = "stock.release.stock.queue")
    public void handle(Message message){

    }


    @Bean
    public AbstractMessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }


    @Bean
    public Exchange stockEventExchange(){
        // String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
        return new TopicExchange("stock-event-exchange",true,false);
    }

    @Bean
    public Queue stockReleaseStockQueue(){
        // String name, boolean durable, boolean exclusive, boolean autoDelete,@Nullable Map<String, Object> arguments
        return new Queue("stock.release.stock.queue",true,false,false,null);
    }

    @Bean
    public Queue stockDelayQueue(){
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange","stock-event-exchange");
        arguments.put("x-dead-letter-routing-key","stock.release");
        arguments.put("x-message-ttl",120000);
        // String name, boolean durable, boolean exclusive, boolean autoDelete,@Nullable Map<String, Object> arguments
        return new Queue("stock.delay.queue",true,false,false,arguments);
    }

    @Bean
    public Binding stockReleaseBinding(){
        // String destination, DestinationType destinationType, String exchange, String routingKey,@Nullable Map<String, Object> arguments
        return new Binding("stock.release.stock.queue",
                Binding.DestinationType.QUEUE,"stock-event-exchange","stock.release.#",null);
    }

    @Bean
    public Binding stockLockedBinding(){
        return new Binding("stock.delay.queue",Binding.DestinationType.QUEUE,
                "stock-event-exchange","stock.locked",null);
    }
}

出现问题: 并没创建交换机、队列、绑定关系

出现问题的原因:只有当第一次连接上RabbitMQ时,发现没有这些东西才会创建

解决方案:监听队列

谷粒商城-订单服务

交换机、队列、绑定关系创建成功后,将上述代码注释

谷粒商城-订单服务

商城业务-订单服务-监听库存解锁

库存解锁的两种场景:

①下单成功,订单过期没有支付被系统自动取消、被用户手动取消。都要解锁

②下单成功,库存锁定成功,接下来的业务调用失败,导致订单回滚。之前锁定的库存就要自动解锁

 加上全参和无参构造器注解

谷粒商城-订单服务

② 保存工作单详情方便回溯

谷粒商城-订单服务③ Common服务中创建To,方便MQ发送消息

谷粒商城-订单服务

如果To仅仅保存这个两个数据的话,会存在一些问题, 当1号订单在1号仓库扣减1件商品成功,2号订单在2号仓库扣减2件商品成功,3号订单在3号仓库扣减3件商品失败时,库存工作单的数据将会回滚,此时,数据库中将查不到1号和2号订单的库存工作单的数据,但是库存扣减是成功的,导致无法解锁库存

解决方案: 保存库存工作详情To

谷粒商城-订单服务

谷粒商城-订单服务④ 向MQ发送库存锁定成功的消息

谷粒商城-订单服务 

谷粒商城-订单服务

商城业务-订单服务-库存解锁逻辑&库存自动解锁完成&测试库存自动解锁

解锁场景:

1.下单成功,库存锁定成功,接下来的业务调用失败导致订单回滚。之前锁定的库存就要自动解锁。

2.锁库存失败无需解锁

解决方案:通过查询订单的锁库存信息,如果有则仅仅说明库存锁定成功,还需判断是否有订单信息,如果有订单信息则判断订单状态,若订单状态已取消则解锁库存,反之:不能解锁库存,如果没有订单信息则需要解锁库存,如果没有锁库存信息则无需任何操作。

1.编写Vo,通过拷贝订单实体,用于接收订单信息

谷粒商城-订单服务

2. 远程服务编写,获取订单状态

谷粒商城-订单服务

谷粒商城-订单服务 3.监听事件

谷粒商城-订单服务

谷粒商城-订单服务

谷粒商城-订单服务

 /**
     * 解锁库存服务
     * @param stockLockedTo
     */
    @Override
    public void unlockStock(StockLockedTo stockLockedTo){
        StockDetailTo detail = stockLockedTo.getDetail();
        Long detailId = detail.getId();
        // 查询库存工作单的信息 有:解锁库存 没有:库存锁定失败,数据自定义回滚无需解锁
        WareOrderTaskDetailEntity detailEntity = wareOrderTaskDetailService.getById(detailId);
        if (null!=detailEntity){
            // 有,解锁库存
            Long id = stockLockedTo.getId(); // 库存工作单的id
            WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getById(id);
            String orderSn = wareOrderTaskEntity.getOrderSn();
            // 远程服务调用,获取订单状态信息
            // 先判断订单是否存在
            R r = orderFeignService.getOrderStatus(orderSn);
            if (r.getCode().equals(0)){
                OrderVo orderVo = r.getData(new TypeReference<OrderVo>() {});
                if (null==orderVo || orderVo.getStatus().equals(4)){
                    // 订单不存在或者订单被关闭,都需要去解锁库存
                    // 当且仅当 锁定状态为 已锁定 时 才去解锁
                    if (detailEntity.getLockStatus().equals(1)){
                        releaseStock(detailEntity.getSkuId(),detailEntity.getWareId(),detail.getSkuNum(),detailEntity.getId());
                    }
                }
            }else {
                // 远程服务调用失败,抛出异常 将消息放回消息队列中
                throw new RuntimeException("远程调用订单服务失败!!!");
            }
        }else {
            // 订单的库存详情信息不存在,无需解锁
        }
    }

    private void releaseStock(Long skuId, Long wareId, Integer skuNum,Long taskDetailId) {
        // 解锁库存
        wareSkuDao.unLockStock(skuId,wareId,skuNum);
        // 更新库存工作单状态
        WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
        wareOrderTaskDetailEntity.setId(taskDetailId);
        wareOrderTaskDetailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(wareOrderTaskDetailEntity);
    }

4. 远程服务调用可能会出现失败,需要设置手动ACK,确保其它服务能消费此消息

#手动ACK设置
spring.rabbitmq.listener.simple.acknowledge-mode=manual

谷粒商城-订单服务

出现问题: 远程调用订单服务时被拦截器拦截

解决方案:请求路径适配放行

谷粒商城-订单服务

商城业务-订单服务-定时关单完成

1.定时关单代码编写

①订单创建成功,给MQ发送关单消息

谷粒商城-订单服务② 监听事件,进行关单

谷粒商城-订单服务

谷粒商城-订单服务谷粒商城-订单服务  

订单释放和库存解锁逻辑: 当订单创建成功之后,向MQ发送关单消息,过期时间为1分钟,向MQ发送解锁库存消息,过期时间为2分钟,关单操作完成之后,过了1分钟解锁库存操作。

存在问题:由于机器卡顿、消息延迟等导致关单消息未延迟发送,解锁库存消息正常发送和监听,导致解锁库存消息被消费,当执行完关单操作后便无法再执行解锁库存操作,导致卡顿的订单永远无法解锁库存。

解决方案:采取主动补偿的策略。当关单操作正常完成之后,主动去发送解锁库存消息给MQ,监听解锁库存消息进行解锁。

谷粒商城-订单服务

③ 按上图创建绑定关系

谷粒商城-订单服务④ common服务中,创建CreateTo(拷贝order实体) 

谷粒商城-订单服务

⑤ 向MQ发送解锁库存消息

谷粒商城-订单服务

⑥ 解锁库存操作

谷粒商城-订单服务谷粒商城-订单服务

商城业务-订单服务-消息丢失、积压、重复等解决方案

谷粒商城-订单服务

情况一: 消息发送出去但是由于网络原因未到达服务器,解决方案:采用try-catch将发送失败的消息持久化到数据库中,采用定期扫描重发的方式。

drop table if exists mq_message;
CREATE TABLE `mq_message` (
	`message_id` CHAR(32) NOT NULL,
	`content` TEXT,
	`to_exchange` VARCHAR(255) DEFAULT NULL,
	`routing_key` VARCHAR(255) DEFAULT NULL,
	`class_type` VARCHAR(255) DEFAULT NULL,
	`message_status` INT(1) DEFAULT '0' COMMENT '0-新建  1-已发送  2-错误抵达  3-已抵达',
	`create_time` DATETIME DEFAULT NULL,
	`update_time` DATETIME DEFAULT NULL,
	PRIMARY KEY (`message_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4

情况二:消息抵达服务器的队列中才算完成消息的持久化,解决方案publish的ack机制

谷粒商城-订单服务情况三: 防止自动ack带来的缺陷,采用手动ack,解决方案上面都有这里不再细说

谷粒商城-订单服务

消息被成功消费,ack时宕机,消息由unack变成ready,Broker又重新发送。解决方案:将消费者的业务消费接口应该设计为幂等性的,比如扣库存有工作单的状态标志。

谷粒商城-订单服务

消息积压即消费者的消费能力不够, 上线更多的消费者进行正常的消费。文章来源地址https://www.toymoban.com/news/detail-403066.html

到了这里,关于谷粒商城-订单服务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 2023最新谷粒商城笔记之支付服务篇(全文总共13万字,超详细)

    这里我们是使用的支付宝进行支付,所以需要调用支付宝的相关API,下面来了解一下怎样使用支付宝进行线上支付。 支付宝开放平台传送门: 支付宝开放平台 网站支付DEMO传送门: 手机网站支付 DEMO | 网页移动应用 RSA、加密加签、密钥等 对称加密 对称加密 :发送方和接收

    2024年02月09日
    浏览(51)
  • 【业务功能篇99】微服务-springcloud-springboot-电商订单模块-生成订单服务-锁定库存

    一个是需要生成订单信息一个是需要生成订单项信息。具体的核心代码为 锁定库存的操作,需要操作ware仓储服务。 没有库存或者锁定库存失败我们通过自定义的异常抛出 如果下订单操作成功(订单数据和订单项数据)我们就会操作锁库存的行为 锁定库存失败通过抛异常来

    2024年02月09日
    浏览(45)
  • 谷粒商城P139集——云服务器frp内网穿透+nginx

    我注册的域名是第一年14元的 (1)购买域名并备案 (2)域名解析 测试:如域名为gulimall.com 则在浏览器中输入 gulimall.com:9200 (前提是9200端口已经开放) (1)下载 wget https://github.com/fatedier/frp/releases/download/v0.20.0/frp_0.20.0_linux_amd64.tar.gz 云服务器下载linux版本注意frps的配置即可

    2024年02月09日
    浏览(65)
  • 谷粒商城第六天-商品服务之分类管理下的获取三级分类树形列表

    目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 在网页中建好目录及菜单 2.1.1 建好商品目录 2.1.2 建好分类管理菜单 ​编辑 2.2 编写组件 2.2.1 先完成组件文件的创建 2.2.2 编写组件 2.2.2.1 显示三级分类树形列表 三、后端部分 3.1 编写商品分类的相关接口 3.1.1 获取树

    2024年02月15日
    浏览(44)
  • 谷粒商城第七天-商品服务之分类管理下的删除、新增以及修改商品分类

    目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 删除功能 2.2 新增功能 2.3 修改功能 三、后端部分 3.1 删除接口 3.2 新增接口 3.3 修改接口 四、总结  删除和新增以及修改的前端无非就是点击按钮,就向后端发送请求,交与后端真正的执行相关操作。 具体来说,就是

    2024年02月15日
    浏览(41)
  • 谷粒商城篇章5 ---- P173-P192 ---- 检索服务【分布式高级篇二】

    目录 1 检索服务  1.1 搭建页面环境 1.1.1 引入依赖 1.1.2 将检索页面放到gulimall-search的src/main/resources/templates/目录下 1.1.3 调整搜索页面 1.1.4 将静态资源放到linux的nginx相关映射目录下/root/docker/nginx/html/static/ search/ 1.1.5 SwitchHosts配置域名转发 1.1.6 测试 1.1.7 nginx配置 1.1.8 网关配

    2024年02月15日
    浏览(45)
  • 谷粒商城第七天-商品服务之分类管理下的分类的拖拽功能的实现

    目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端实现 2.1 判断是否能进行拖拽 2.2 收集受影响的节点,提交给服务器 三、后端实现 四、总结 这个拖拽功能对于这种树形的列表,整体的搬迁是很方便的。但是其实现却并不是那么的简单。 花样主要体现在前端上面,前端有两

    2024年02月14日
    浏览(97)
  • 【业务功能篇104】 补充【业务功能篇99】微服务-springcloud-springboot-电商订单模块--整合支付

    在前面我们业务功能篇98-99中,我们介绍了电商项目中的订单模块服务,那么最后就是需要进行支付动作,那么我们这里就通过订阅第三方平台支付宝的支付调用接口功能,来进一步完成订单提交后的支付动作,支付宝的接口使用可以登录官网开发指南详情去了解 在我们对应

    2024年02月09日
    浏览(48)
  • 谷粒商城第三天-微服务中基本组件的使用 java.lang.AbstractMethodError: org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.cho

    目录 一、前言 二、学习的内容 一、Nacos的服务注册/发现 1. 导依赖,nacos-discovery java.lang.AbstractMethodError: org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.cho 2. 在application.yml中声明nacos服务器的ip地址和端口号,以及指定好服务的名称 3. 在启动类上面加上@EnableDiscoverClient 二、

    2024年02月09日
    浏览(82)
  • 谷粒商城(三)

    简介 全文搜索属于最常见的需求,开源的 Elasticsearch 是目前全文搜索引擎的首选,它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。 Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。Elastic 是 Lucene

    2024年02月10日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包