zoukankan      html  css  js  c++  java
  • 分布式系统知识点五:分布式事务及其解决方案2 利用消息队列处理分布式事务(转载)

    摘要:本系列为网上收集转载分布式相关知识点系列文章,并非原创。如果侵权,请联系我删除!!!

    引言

    这篇说说分布式事务的问题。企业现在的架构都由传统的架构转向了微服务架构,如下图所示:

    那么,都不可避免的会遇到跨数据库调用的,分布式事务问题!
    目前,业内解决分布式事务问题,都基本不用JTA这种强一致性的解决方案,基本是采用如下两套方案

    • 基于TCC的事务框架
    • 消息队列

    OK,你们先记住两点
    (1)图中的服务A和服务B,如果是同步调用,要求一起成功,或者一起失败,那么此时应选用TCC的事务框架,这点我改天另写一篇,先挖坑!
    (2)图中的服务A和服务B,如果是异步调用,比如服务C先调用服务A后,服务C不用管服务B的执行结果,直接返回,那么这种情况下,应选用消息队列!这篇文章重点讲!
    目前为止,大部分文章都讲的太复杂了。导致很多新人看完后于是看这篇文章前,你们先忘记你们在其他文章看到的概念,跟着博主的思路走!

    正文

    先给大家套一个业务场景,也是很常见的一个异步调用场景:

    • 支付宝往余额宝转钱

    即将服务A假设为支付宝,服务B假设为余额宝。
    于是呢,我们的支付宝往余额宝转100块钱是怎么做的呢?
    特别容易,借助消息队列即可,如下图所示

    一致性解决

    OK,上面这一版有一个致命的问题!如下所示
    事务开始
    (1)给支付宝账户zhangsan,扣100元
    (2)将(给余额宝账户zhangsan,加100元)封装为消息,发送给消息队列
    事务结束

    敢问你,如何保证第一步和第二步是在同一个事务里完成的。换句话说,第一步操作的是数据库,第二步操作的是一个消息队列,你如何保证这两步之间的一致性?
    记住了,任何涉及到数据库和中间件之间的业务逻辑操作,都需要考虑二者之间的一致性。比如,你先操作了数据库,再操作缓存,数据库和缓存之间一致性如何解决?好吧,如果是博主的铁粉,应该知道怎么解决了,回到我们的场景。
    改变思路,加一张事务表,如下图所示

    注意了,此时事务的内容为
    事务开始
    (1)给支付宝账户zhangsan,扣100元
    (2)给事件表插入一条记录
    事务结束

    此时是对同一数据库的两张表操作,因此可以用数据库的事务进行保证。
    另外,起一个定时程序,定时扫描事务表,发现一个状态为'UNFINISHED'的事件,就进行封装为消息,发送到消息中间件,然后将状态改为'FINISHED'.

    幂等性解决

    注意了,这一版还存在一个幂等性问题!
    仔细看,定时程序做了如下三个操作
    (1)定时扫描事务表,发现一个状态为'UNFINISHED'的事件
    (2)将事件信息,封装为消息,发送到消息中间件
    (3)将事件状态改为'FINISHED'

    OK,假设在步骤(2)的时候,发送完消息体,还未执行步骤(3),定时程序阵亡了!然后重启定时程序,发现刚那个事务的状态依然为'UNFINISHED',因此重新发送。这样,就会出现重复消费问题。因此,幂等性也是需要保证的!

    如果是博主的忠实读者,应该知道,博主曾经写过一篇《分布式之消息队列复习精讲》,里头就提到了如何解决幂等性问题。什么?你没看过这篇?拉出去枪毙!
    借用这篇文章里的方案。在消费者端,也维护一个带主键的表,可以选txid为主键,如下图所示

    如果一旦出现重复消费,则在事务里直接报出主键冲突错误,从而保证了幂等性!

    面试连环炮

    面试官:"你们用了微服务架构么?"
    求职者:"用了,用了"
    面试官:"怎么解决分布式事务的啊?"
    求职者:"我们的服务刚好是异步的场景,所以用消息队列!"
    面试官:"怎么保证一致性和幂等性啊?"
    求职者:"嗯,听我细细说来....."

    来源:https://www.cnblogs.com/rjzheng/p/10115798.html

    引言

    在上篇文章《老生常谈——利用消息队列处理分布式事务》一文中留了一个坑,今天来填坑。如下图所示

    如果服务A和服务B之间是同步调用,比如服务C需要按流程调服务A和服务B,服务A和服务B要么一起成功,要么一起失败。
    针对这种情况,目前业内普遍推荐使用TCC事务来解决的!

    正文

    ok,老规矩,我们先套一个业务场景进去,如下图所示

    那页面点了支付按钮,调用支付服务,那我们后台要实现下面三个步骤
    [1] 订单服务-修改订单状态
    [2] 账户服务-扣减金钱
    [3] 库存服务-扣减库存
    达到事务的效果,要么一起成功,要么一起失败!就要采取TCC分布式事务方案!

    概念

    TCC的全称是(Try-Confirm-Cancel)。如下图所示

    ps:TCC又可以被称为两阶段补偿事务,第一阶段try只是预留资源,第二阶段要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的confirm/cancel,用来清除第一阶段的影响,所以叫补偿型事务。

    再打个比方,说TCC太高大上是吧,讲RM中的prepare、commit、rollback接口,总知道吧。可以类比的这么理解

    那差别在哪呢?
    rollback、commit、prepare,站在开发者层面是感知不到的,数据库帮你做了资源的操作!
    而try、confirm、cancel,站在开发者层面是能感知到的,这三个方法的业务逻辑,即对资源的操作,开发者是要自己去实现的!
    好,下面套入我们的场景,怎么做呢。比如,你的订单服务中本来只有一个接口

    //修改代码状态
    orderClient.updateStatus();
    

    都要拆为三个接口,即

    orderClient.tryUpateStatus();
    orderClient.confirmUpateStatus();
    orderClient.cancelUpateStatus();
    

    注意了:面试官如果问你,TCC有什么缺点?这就是很严重的缺点,对代码入侵性大!每套业务逻辑、都要按try(请求资源)、confirm(操作资源)、cancel(取消资源),拆分为三个接口!

    具体每个阶段,每个服务业务逻辑是什么样的呢?
    假设,库存数量本来是50,那么可销售库存也是50。账户余额为50,可用余额也为50。用户下单,买了1个单价为1元的商品。流程如下:
    Try阶段
    订单服务:修改订单的状态为支付中
    账户服务:账户余额不变,可用余额减1,然后将1这个数字冻结在一个单独的字段里
    库存服务:库存数量不变,可销售库存减1,然后将1这个数字冻结在一个单独的字段里
    confirm阶段
    订单服务:修改订单的状态为支付完成
    账户服务:账户余额变为(当前值减冻结字段的值),可用余额不变(Try阶段减过了),冻结字段清0。
    库存服务:库存变为(当前值减冻结字段的值),可销售库存不变(Try阶段减过了),冻结字段清0。
    cancel阶段
    订单服务:修改订单的状态为未支付
    账户服务:账户余额不变,可用余额变为(当前值加冻结字段的值),冻结字段清0。
    库存服务:库存不变,可销售库存变为(当前值加冻结字段的值),冻结字段清0。

    伪代码

    接下来从代码程序来说明,为了便于演示,将入参略去。
    本来,你支付服务的代码是长下面这样的

    那么,用上TCC模型后,代码变成下面这样

    注意了,这种写法其实严格上来说,不是不行。看你业务场景,因为存在一些瑕疵,看你自己有没办法接受
    (1)cancel或者confirm出现异常了,你怎么处理?
    例如在cancel阶段执行如下三行代码

    orderClient.cancelUpdateStatus();
    accountClient.cancelDecrease();
    repositoryClient.cancelDecrease();
    

    你第二行出现异常了,第三行没跑就退出了,怎么办?你要对此进行业务补偿!
    (2)大量逻辑重复
    你看啊,我们的执行架构其实是这样的

    try{
        xxclient.try();
    }catch(Throwable t){
        xxclient.cancel();
        throw t;
    }
    xxclient.confirm();
    

    有没办法让这个架子交给框架去执行,我们告诉框架,你在每个阶段要执行哪些方法就好!

    因此,需要引入TCC分布式事务框架,事务的Try、Confirm、Cancel三个状态交给框架来感知!你只要告诉框架,Try要执行啥,Confirm要执行啥,Cancel要执行啥!如果Cancel过程出现异常了,框架有内部的补偿措施给你恢复数据!
    以分布式tcc框架hmily为例,如果出现cancel异常或者confirm异常的情况,在try阶段会保存好日志,Hmily有内置的调度线程池来进行恢复,不用担心。
    那hmily,怎么感知状态的呢?也很简单,就是切面编程,核心逻辑如下几行
    image
    我们在使用过程中,只要通过@Tcc注解告诉框架confirm方法执行啥,cancel方法执行啥即可!其他的交给框架帮你处理!
    好了,不多说了,再说下去就是hmily源码解析了,大家有空自己去了解!

    挖坑

    注意看,《老生常谈——利用消息队列处理分布式事务》和本文所涉及到的微服务都是同一平台的。
    那如果碰到,不同平台之间调用,你要怎么保证事务?比如,我的服务要调银行接口,你觉得可能让银行接你的tcc框架么?或者让银行接你的消息对列?你们觉得现实么?当然,有的人会说:"一个http直接调了。"嗯,少年有想法!
    ok,那么业内针对这种涉及到第三方接口的服务调用,如何保证一致性?大家好好思考。

    来源 https://www.cnblogs.com/rjzheng/p/10164667.html

  • 相关阅读:
    色彩空间RGB/CMYK/HSL/HSB/HSV/Lab/YUV基础理论及转换方法:RGB与YUV
    三色视者与四色视者身后的理论基础:色彩原理
    再谈设计原则—7种设计原则学习总结笔记
    sass安装:webpack sass编译失败,node-sass安装失败的终极解决方
    再谈Java数据结构—分析底层实现与应用注意事项
    再谈js对象数据结构底层实现原理-object array map set
    浮点数精度问题透析:小数计算不准确+浮点数精度丢失根源
    再谈编程范式—程序语言背后的思想
    再谈循环&迭代&回溯&递归&递推这些基本概念
    再谈MV*(MVVM MVP MVC)模式的设计原理—封装与解耦
  • 原文地址:https://www.cnblogs.com/yylingyao/p/12707716.html
Copyright © 2011-2022 走看看