分布式事务

1:分布式事务

使用了 seata AT事务

image-20201111140613253

未使用 seata 事务

image-20201111141616257

一: seata: AT 模式

http://seata.io/zh-cn/docs/dev/mode/at-mode.html

优点:业务改造成本低 隔离性好

缺点:性能差

1: 没有强一致性要求的业务推荐不使用分布式事务, 推荐使用日志的方式进行手动回滚或者通过补偿方式

Seata开销:seata AT模式 开启了全局锁

一阶段

1: 内存计算开销

2: 一条Update的SQL,则需要全局事务xid获取、before image解析SQL,查询一次数据库、after image查询一次数据库、

insert undo_log写一次数据库、before commit与TC通讯,判断锁冲突,这些操作都需要一次远程通讯RPC,而且是同步的。

3: 数据库表undo_log写入时 **rollback_info longblob NOT NULL COMMENT ‘rollback info’ ** 字段的插入性能也是不高的。

4: 每条写SQL都会增加这么多开销。

二阶段

二阶段虽然是异步的,但其实也会占用系统资源,网络、线程、数据库。

总结

全局锁的引入实现了隔离性,但带来的问题就是阻塞,降低并发性,尤其是热点数据,这个问题会更加严重。

为了进行自动补偿,需要对所有交易生成前(after image) 后(before image)镜像并持久化。

在实际业务场景下,这个是成功率有多高,或者说分布式事务失败需要回滚的有多少比例?

这个比例在不同场景下是不一样的,考虑到执行事务编排前,很多都会校验业务的正确性,所以发生回滚的概率其实相对较低。

按照二八原则预估,即为了20%的业务回滚,需要将80%的成功操作的响应时间增加数倍,这个代价过高.

因此在不需要保证强一致性的业务场景下,就不要采用事务.

在事务执行中必须将所有的资源锁定, 对于长事务来说,整个事务期间对数据的独占,将严重影响系统并发性能。 因此,在高并发场景中,对ACID的部分特性进行放松从而提高性能,这便产生了BASE柔性事务。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。另外提供自动的异常恢复机制,可以在发生异常后也能确保事务的最终一致。

如果需要保证强一致性的话推荐使用 seata tcc 事务 采用手写 commitMethod 提交方法 rollbackMethod 回滚方法

二: seata: TCC模式 hmily: TCC 事务

三个阶段模拟减库存:

  1. try : 先不要直接减库存,可以用某个字段表示先冻结调需要扣减的部分
  2. confirm:如果try成功,则执行confirm逻辑,confirm里面则开始真正的扣减库存
  3. cancle: 如果try失败,则执行cancle实现代码,回滚之前的冻结数据。

tcc 事务要求:

1: 需要做资源的检查和预留。

2: Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这时会触发 Cancel 接口,这时Cancel

执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应

的业务数据可以进行回滚。

3: 防悬挂控制 Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又

收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此

时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条

记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。

tcc事务写法
@LocalTCC
public interface TccActionOne {

    /**
     * 准备阶段.
     * @param actionContext the action context
     * @param a             the a
     * @return boolean
     */
    @TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
    public boolean prepare(BusinessActionContext actionContext,
                           @BusinessActionContextParameter(paramName = "a") int a, @BusinessActionContextParameter(paramName = "commodityCode") String commodityCode, int count);

    /**
     * commit 提交
     * @param actionContext
     * @return boolean
     */
    public boolean commit(BusinessActionContext actionContext);

    /**
     * Rollback 回滚
     * @param actionContext
     * @return boolean
     */
    public boolean rollback(BusinessActionContext actionContext);




@Override
    public boolean prepare(BusinessActionContext actionContext, int a, String commodityCode, int count) {
        System.out.println("xid-----------------:" + actionContext.getXid());
        Storage storage1 = new Storage().setCommodityCode(String.valueOf(commodityCode));
        synchronized (this) {
            List<Storage> storages = storageDAO.selectList(Wrappers.query(storage1));
            if (storages != null && storages.size() > 0) {
                return true;
            }
            Storage storage = new Storage();
            storage.setCommodityCode(String.valueOf(commodityCode));
            storage.setCount(1L);
            storageDAO.insert(storage);
        }
        // 防止事务悬挂,先查询事务状态
        Transacton transaction = new Transacton();
        transaction.setTxId(actionContext.getXid());
        transaction.setActionId(String.valueOf(actionContext.getBranchId()));
        transaction.setState("3");
        List<Transacton> transactons = transactonDao.selectList(Wrappers.query(transaction));
        if (transactons.size() > 1) {
            return true;
        }
        transaction.setState("1");
        transactonDao.insert(transaction);
        System.out.println("try:"+commodityCode);
        return true;
    }

    @Override
    public boolean commit(BusinessActionContext actionContext) {
        Object commodityCode = actionContext.getActionContext().get("commodityCode");
        System.out.println("xid-----------------:"+actionContext.getXid());
        Storage storage1 = new Storage().setCommodityCode(String.valueOf(commodityCode));
        synchronized (this){
            List<Storage> storages = storageDAO.selectList(Wrappers.query(storage1));
            if (storages != null && storages.size() > 0) {
                System.out.println("重试机制,code为:"+commodityCode);
                return true;
            }
            Storage storage = new Storage();
            storage.setCommodityCode(String.valueOf(commodityCode));
            storage.setCount(1L);
            storageDAO.insert(storage);
        }
        try {
            Transacton transaction = new Transacton();
            transaction.setTxId(actionContext.getXid());
            transaction.setActionId(String.valueOf(actionContext.getBranchId()));
            transaction.setState("2");
            transactonDao.insert(transaction);
        }catch (Exception e){}

        return true;
    }

    @Override
    public boolean rollback(BusinessActionContext actionContext) {
        Object commodityCode = actionContext.getActionContext().get("commodityCode");
        System.out.println("rollback:"+commodityCode);
        Storage storage1 = new Storage().setCommodityCode(String.valueOf(commodityCode));
        storageDAO.delete(Wrappers.query(storage1));
        String xid = actionContext.getXid();
        ResultHolder.setActionOneResult(xid, "T");
        return true;
    }

总结

接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,

并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,

但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。

2:JVM优化

CMS收集器只收集老年代,其以吞吐量为代价换取收集速度。由于项目特性需要以吞吐量为主,而 CMS是以吞吐量为代价换取收集速度.

G2收集器是以吞吐量为主,并且可以预测停顿时间. 通过修改最小停顿时间和堆内存大小来进行性能调优

CMS:以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现

过程:

1、初始标记:独占PUC,仅标记GCroots能直接关联的对象

2、并发标记:可以和用户线程并行执行,标记所有可达对象

3、重新标记:独占CPU(STW),对并发标记阶段用户线程运行产生的垃圾对象进行标记修正

4、并发清理:可以和用户线程并行执行,清理垃圾

优点:

并发,低停顿

缺点:

1、对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢

2、无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾

3、CMS使用“标记-清理”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC,为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行FullGC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了

CMS 出现FullGC的原因:

1、年轻带晋升到老年带没有足够的连续空间,很有可能是内存碎片导致的

2、在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发FullGC

 

G1:是一款面向服务端应用的垃圾收集器

特点:

1、并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2、分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。

3、空间整合:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。

4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。


与其它收集器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值