Day107 Java项目 (SSM+Dubbo)商城(十六) 分布式事务解决方案

1. 结算页商品清单

一.需求分析

  1. 在订单结算页显示送货清单、合计金额、优惠金额。

    显示购物清单应该刷新购物车,从数据库中查询最新价格计算。
    下方显示合计件数与合计金额。

二.实现思路

  1. 后端新增方法,获取购物车列表循环更新每个商品的价格,刷新数据后更新购物车,并返回更新后的购物车数据。
  2. 前端获取列表后循环显示,显示合计件数与合计金额参考购物车页面的实现方式。

三.后端代码

  1. CartService新增方法定义
  2. CartServiceImpl实现该方法
  3. CartController新增方法

四.前端代码

  1. 将order.html拷贝至qingcheng_web_portal
  2. 修改order.html,为div添加id

    添加js代码
  3. 修改页面中购物车列表部分

    修改页面合计数部分

2. 收货地址选择

一.需求分析

  1. 在结算页上列出收货地址

    点击收货人姓名后,即选中该收货人,在结算页下方显示地址、收货人姓名和电话

二.实现思路

  1. 首先我们先看一下user数据库的收货地址表 ( tb_address 表)表结构

    后端实现根据当前登陆人查询收货地址列表的方法,前端使用Vue.js进行渲染。
    选择收货地址通过vue.js实现。

三.收货地址列表

  1. AddressService新增方法定义
  2. AddressServiceImpl实现方法
  3. CartController新增方法
  4. 修改 cart.html ,增加属性,用于存储地址列表
  5. order.html新增方法,用于查询地址列表
  6. 修改order.html的地址列表部分

四.选择收货地址

  1. order.html 新增属性,用于保存订单(订单包含了地址、手机号、联系人等信息)
  2. order.html新增方法,用于选择地址
  3. 修改order.html地址显示区域
  4. 修改order.html的findAddressList方法,在回调后添加以下代码,实现默认地址的显示
  5. 修改地址列表,增加样式和点击事件的调用

3. 提交订单

一.需求分析

  1. 选择支付方式

    点击“提交订单”按钮,完成订单保存动作。

    将购物车中选中项保存为订单。如果是在线支付则跳转到支付页面,如果是货到付款则直接跳转到完成页。
    保存订单前需要进行库存进行检查和扣减,如果库存不足则不能下单。
    注意:在提交保存订单时,需要再次调用刷新购物车的方法。

二.实现思路

  1. 首先我们先看一下相关的表结构
    tb_sku 表
  2. tb_order 表(订单主表)




  3. tb_order_item 表(订单明细表)
  4. 刷新并获取购物车
  5. 检查库存与扣减库存,增加销量
  6. 保存订单主表与明细表,明细表的数据来自购物车
  7. 清除选中的购物车数据

三.库存扣减逻辑

  1. SkuMapper新增方法
  2. SkuService新增方法
  3. SkuServiceImpl实现此方法

四.保存订单逻辑

  1. 修改OrderService的add方法的定义,修改返回值,用于返回订单号和金额
  2. 修改OrderServiceImpl的add方法

  3. 订单服务添加 applicationContext-service.xml
  4. CartController新增方法

五.前端代码

  1. 修改cart.html的order属性,增加payType并设置默认值为1
  2. 修改cart.html的支付方式选择部分
  3. 对留言文本框进行绑定
  4. 新增方法
  5. 调用方法

六.结果页面

  1. 从静态原型中拷贝三个页面到web_portal 分别是pay.html 、order-success.html 、order-fail.html
  2. pay.html 和order-success.html 添加vue.js代码
  3. 绑定

4. 分布式事务解决方案

刚才我们编写的扣减库存与保存订单是在两个服务中存在的,如果扣减库存后订单保存失败了是不会回滚的,这样就会造成数据不一致的情况,这其实就是我们所说的分布式事务的问题,接下来我们来学习分布式事务的解决方案。

一.本地事务与分布式事务

  1. 事务
    1. 数据库事务(简称:事务,Transaction)是指数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
    2. 事务拥有以下四个特性,习惯上被称为ACID特性:
      1.原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
      2.一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态是指数据库中的数据应满足完整性约束。除此之外,一致性还有另外一层语义,就是事务的中间状态不能被观察到(这层语义也有说应该属于原子性)。
      3.隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行,如同只有这一个操作在被数据库所执行一样。
      4.持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中.在事务结束时,此操作将不可逆转。
  2. 本地事务
    起初,事务仅限于对单一数据库资源的访问控制,架构服务化以后,事务的概念延伸到了服务中。倘若将一个单一的服务操作作为一个事务,那么整个服务操作只能涉及一个单一的数据库资源,这类基于单个服务单一数据库资源访问的事务,被称为本地事务(LocalTransaction)。
  3. 分布式事务
    1. 分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,且属于不同的应用,分布式事务需要保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
    2. 最早的分布式事务应用架构很简单,不涉及服务间的访问调用,仅仅是服务内操作涉及到对多个数据库资源的访问。
    3. 当一个服务操作访问不同的数据库资源,又希望对它们的访问具有事务特性时,就需要采用分布式事务来协调所有的事务参与者。
    4. 对于上面介绍的分布式事务应用架构,尽管一个服务操作会访问多个数据库资源,但是毕竟整个事务还是控制在单一服务的内部。如果一个服务操作需要调用另外一个服务,这时的事务就需要跨越多个服务了。在这种情况下,起始于某个服务的事务在调用另外一个服务的时候,需要以某种机制流转到另外一个服务,从而使被调用的服务访问的资源也自动加入到该事务当中来。
    5. 如果将上面这两种场景(一个服务可以调用多个数据库资源,也可以调用其他服务)结合在一起,对此进行延伸,整个分布式事务的参与者将会组成如下图所示的树形拓扑结构。在一个跨服务的分布式事务中,事务的发起者和提交均系同一个,它可以是整个调用的客户端,也可以是客户端最先调用的那个服务。
    6. 较之基于单一数据库资源访问的本地事务,分布式事务的应用架构更为复杂。在不同的分布式应用架构下,实现一个分布式事务要考虑的问题并不完全一样,比如对多资源的协调、事务的跨服务传播等,实现机制也是复杂多变。

二.分布式事务相关理论

  1. CAP定理
    1. CAP定理是在 1998年加州大学的计算机科学家 Eric Brewer (埃里克.布鲁尔)提出,分布式系统有三个指标
      Consistency 一致性
      Availability 可用性
      Partition tolerance 分区容错
      它们的第一个字母分别是 C、A、P。Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 CAP 定理。
    2. 分区容错 Partition tolerance
      大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。

      上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
      一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。
    3. 可用性 Availability
      Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应。用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
    4. 一致性 Consistency
      Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。
                            
      问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
      为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求G2 也改成 v1。
    5. 一致性和可用性的矛盾
      一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。
      如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性。如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
    6. 综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
  2. BASE理论
    1. BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventuallyconsistent(最终一致性)三个短语的缩写,来自 ebay 的架构师提出。BASE 理论是对CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于CAP 定理逐步演化而来的。其核心思想是:
    2. Basically Available(基本可用)
      什么是基本可用呢?假设系统,出现了不可预知的故障,但还是能用,相比较正常的系统而言:
      1. 响应时间上的损失:正常情况下的搜索引擎 0.5 秒即返回给用户结果,而基本可用的搜索引擎可以在 1 秒作用返回结果。
      2. 功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
    3. Soft state(软状态)
      什么是软状态呢?相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。
      软状态指的是:允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。
    4. Eventually consistent(最终一致性)
      系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。

三.分布式事务解决方案

  1. 基于XA协议的两阶段提交
    首先我们来简要看下分布式事务处理的XA规范 :

    可知XA规范中分布式事务有AP,RM,TM组成:
    - 1 - 其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务开始和结束)并访问事务边界内的资源。
    - 2 - 资源管理器(Resource Manager,简称RM):Rm管理计算机共享的资源,许多软件都可以去访问这些资源,资源包含比如数据库、文件系统、打印机服务器等。
    - 3 - 事务管理器(Transaction Manager ,简称TM):负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。
    二阶段协议:
    1. 第一阶段TM要求所有的RM准备提交对应的事务分支,询问RM是否有能力保证成功的提交事务分支,RM根据自己的情况,如果判断自己进行的工作可以被提交,那就就对工作内容进行持久化,并给TM回执OK;否者给TM的回执NO。RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了。
    2. 第二阶段TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare回执NO的话,则TM通知所有RM回滚自己的事务分支。
    3. 也就是TM与RM之间是通过两阶段提交协议进行交互的。
    4. 优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)
      缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
  2. TCC补偿机制
    TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
    1. Try 阶段主要是对业务系统做检测及资源预留
    2. Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
    3. Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
    4. 例如: A要向 B 转账,思路大概是:

      优点: 相比两阶段提交,可用性比较强
      缺点: 数据的一致性要差一些。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
  3. 消息最终一致性
    消息最终一致性应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:

    基本思路就是:
    - 1 - 消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。
    - 2 - 消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
    - 3 - 生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
    优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
    缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

5. 库存扣减分布式事务的实现

一.需求分析

  1. 如果订单在创建时发生了异常,此时库存已经扣减了,这样库存就比实际中的数量要少,造成数据不一致,为了避免这种情况,我们需要采用消息最终一致性的分布式事务解决方案。

二.实现思路

  1. 当订单服务发生异常时,发送消息给mq ,消息内容为购物车数据,用于恢复库存
  2. 商品服务从mq提取消息,保存到库存回滚表中
  3. 在管理后台开启定时任务,定时扫描库存回滚表执行库存回滚。
  4. 下面我们来看一下库存回滚表tb_stock_back的设计

    为什么要设置成联合主键的形式呢? 因为消息在发送时,可能会因为失败而重复发送,而重复发送可能造成回滚数据的重复。订单编号+sku编号组合起来如果相同一定是同一笔数据 ,所以我们将其设置成联合主键,这样在插入重复数据时就会因为主键唯一冲突而无法插入。

三.发送库存回滚消息

  1. qingcheng_service_order工程pom.xml引入依赖
  2. 添加配置文件applicationContext-rabbitmq-producer.xml
  3. 修改OrderServiceImpl的add方法

四.生成库存回滚记录

  1. 实现思路:从消息队列中取出购物车列表json,转换为列表后查询到库存回滚表。
  2. qingcheng_service_goods工程pom.xml引入依赖
  3. 新增StockBack实体类
  4. 新增StockBack数据访问接口
  5. StockBackService新增方法定义
  6. StockBackServiceImpl实现方法
  7. 编写消费端,从消息中取出消息,转换json字符串并调用服务方法
  8. qingcheng_service_goods添加配置文件applicationContext-rabbitmqconsumer.xml

五.定时执行库存回滚

  1. 实现思路:编写定时任务,间隔一小时执行库存回滚。查询库存回滚记录表中状态为0的记录,执行回滚逻辑。
  2. StockBackService新增方法定义
  3. StockBackServiceImpl实现方法
  4. qingcheng_web_manager添加任务调度类
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值