美文网首页
微服务架构实战笔记--分布式事务(同步实现)

微服务架构实战笔记--分布式事务(同步实现)

作者: 滨岩 | 来源:发表于2020-03-24 11:23 被阅读0次

    同步场景分布式事务设计

    同步场景

    1、首页推荐商品列表
    a、商品信息
    b、用户信息
    c、社交信息

    2、购买商品
    a、下单->A
    b、减库存->B
    c、支付->C

    image.png

    首页推荐商品列表里面包含,
    1、商品的图片信息、价格、标题和描述;这些都是商品本身的信息,除了商品的本身信息;
    2、商品发布人的信息:本人的头像、姓名、好友;
    3、商品社交信息:点赞、留言

    这个时候,APP拿到数据是发了一个请求还是多个请求呢?
    很显然我们是发了一个请求,发三个请求肯定是不好的,因为发三个请求,延迟会变高,传输数据量也会变大。再者商品的展示其实是有一定的过滤规则的,比如说我一页是10条,我不允许在一页里面同一个人的商品出现两次,这就需要通过过滤来实现,如果只是随便从DB中取出10条,极端情况下有可能你拿出的10条商品都是同一个人发布的,一过滤就变成一条了,这就不符合我们的规则,所以肯定要一次多取一些,比如拿100条,再APP端再做一些排列组合,这样对APP来说,无论流量或者延迟来说都是浪费的。

    所以在这种情况下更好的方式是:聚合操作,放到Server端去做,这时候我们的架构如下图所示:

    image.png

    APP端发送商品列表请求到网关层,网关层路由到聚合服务层,聚合服务依次查询用户服务、用户服务、社交服务获取商品信息(图片、价格、标题)、用户信息(头像、姓名)、社交信息(评论、点赞);这就是一个读同步场景:商品->用户->社交;读的分布式事务一般意义不大,所以分布式事务一般是针对写的。

    2、购买商品架构方案

    a、下单->A
    b、减库存->B
    c、支付->C


    image.png
    同步场景分布式事务设计

    解决方案

    基于异步补偿的分布式事务
    架构设计的三大关键点

    1、记录请求调用链条
    2、提供幂等补偿接口
    3、基于补偿机制

    比如上图的微服务架构,下单成功、减库存成功、支付失败,此时需要会滚,其中支付不用回滚,因为支付是本地事务,它会自动回滚,这时候呢有两种补偿办法:
    1、第一种:聚合服务直接返回APP,告诉它这次请求失败了!然后再异步的补偿下单、补偿库存。
    2、第二种:一旦支付业务层失败了,首先补偿库存、补偿下单,补偿成功以后再告诉APP,这次请求失败了。

    很显然这是Fast Failture 快速失败原则,反正已经失败了,为什么还要业务方去等待呢,第一种方案是异步的,不会阻塞正常的业务流程。

    这其实都是Saga 的“向后恢复”模式:补偿所有已完成的事务,如果任一事务失败!
    而不是“向前恢复”;

    如何补偿呢?
    减库存,理论上我们是要加1 ,我们需要记录减库存的上下文;
    下单,我们需要将订单取消;
    支付:虽然支付失败,也要将失败记录记录下来

    为什么要提供幂等的补偿接口呢?
    例如减库存补偿的时候,补偿失败重试的时候一直加1、加1、加1....问题就变得很复杂,库存多加了很多。

    image.png

    比如A->B->C 那么每次只需前都要 通过Porxy 将调用参数写入TDB 库中;
    执行A的本地事务,先将A的参数写入到数据库TDB中,然后写入A的本地事务
    执行B的本地事务,先将B的参数写入到数据库TDB中,然后写入B的本地事务
    执行C的本地事务,先将C的参数写入到数据库TDB中,然后写入C的本地事务

    写入的记录格式为:tid(事务ID)->state(状态值)->ts(时间)

    tschedule 会每隔5秒去扫描事务日志数据库是否有状态为要补偿的记录;
    然后去补偿事务,补偿完成之后将事务的状态修改为“已补偿”;

    rpc client 主要用来去调用“数据访问层”的补偿接口;

    业务逻辑层Proxy设计(基于AOP实现)

    1、逻辑层调用上加上事务注解@Around("execution(**(..))&&@annotation(TX)")
    2、Proxy在真正业务逻辑被调用之前,生成一个全局唯一TXID标示事务组,TXID保存在ThreadLocal变量里,方法开始前写入,完成后清除,并向远端数据库写入TXID并把事务组置为开始状态;

    3、业务逻辑层调用数据访问层之前,通过RPCProxy代理记录当前调用请求参数;
    4、如果业务正常,调用完成后,当前方法的调用记录存档或删除
    5、如果业务异常,查询调用链反向补偿

    image.png
    数据访问层设计

    1、原子接口
    2、补偿接口
    a、谁来提供?
    b、幂等性保证
    3、基于接口方法,在方法名加注解标注补偿方法名
    4、@Compensable(cancelMethod="cancelRecord")

    比如:数据访问层的Order方法,它的补偿方法是cancelRecord,当然我们会打一个注解 Compensable,这个Order方法怎么样跟我的cancelRecord方法匹配起来,因为数据访问层在启动的时候,会通过Spring boot的扫包这样一些component-scan;这些记录可以放在配置中心里面,这个配置中心就有两条记录:一个是源方法 一个是补偿方法
    K-V记录:order:cancelRecord

    分布式事务补偿服务

    1、事务组表(数据库表TDB)
    a、记录事务组状态
    b、txid、state、timestamp;

    2、事务调用组表(数据库表TDB)
    a、记录事务组内的每一次调用以及相关参数
    b、Txid、actionid、callmethod、pramatype、params

    比如A/B/C ,Txid为事务id,actionId:为第一步、第二步、第三步;
    callmethod:补偿方法,可以从配置中心获取,数据访问层启动的时候回将每一个接口对应的补偿方法,通过注解的方式关联起来,放到配置中心;
    业务层的Proxy 比如调用A方法,就通过配置中心可以拿到A方法的补偿方法:源方法A|补偿方法CA
    paramtype:服务的类型,有可能你是web服务值就为1,有可能是RPC服务值就为2;
    params:参数

    事务组表:
    txid state timestamp
    1 1 2020-03-24
    事务调用组表
    Txid actionid callmethod pramatype params
    t1 1 CA 2(1、web;2、RPC) PA
    t1 2 CB 2 PB
    t1 3 CC 2 PC

    如果补偿成功就将 事务组表的状修改为 2

    txid state timestamp
    1 2 2020-03-24

    事务调用组表中因为 C是最后一步,所以CC无需补偿;如果CB补偿失败,因为CB是同一个事务所以会滚就行;所以问题在CA上;这时候不能再弄个分布式事务来补偿未补偿成功的事务,这时候要记录失败日志、报警、人工介入;

    3、补偿策略
    a、调用执行失败,修改事务组状态
    b、分布式事务补偿服务异步执行补偿

    分布式事务成功案例

    商品交易创建订单事务组正常流程

    1、锁库存->减红包->创建订单

    2、代理层透明记录调用请求参数
    a、记录事务域的开始与结束
    b、在所有远程调用成功时
    c、对业务逻辑不做侵入

    image.png

    分布式事务失败案例

    商品交易创建订单事务异常流程

    1、微服务数据访问层失败,代理更改事务组状态
    2、微服务业务正常执行
    3、事务补偿服务异步执行补偿

    image.png

    相关文章

      网友评论

          本文标题:微服务架构实战笔记--分布式事务(同步实现)

          本文链接:https://www.haomeiwen.com/subject/egqoyhtx.html