美文网首页
微服务 17: Seata AT模式 并发测试(下)(文末有项目

微服务 17: Seata AT模式 并发测试(下)(文末有项目

作者: _River_ | 来源:发表于2021-04-18 21:48 被阅读0次
 文章知识来源主要来源于:赵俊夫先生的博客  以下为原文链接
https://blog.csdn.net/u011177064/category_9572944.html
本章介绍
主要为了测试 Seata AT模式 应对不正常 不正常  不正常 并发的 正常处理
1:进行并发测试 原接口添加@Transactional(或者不加事务 )
在原调用接口修改 @GlobalTransactional 为 @Transactional

在减少库存的逻辑 添加日志
    @Override
    public void deductStorage(String commodityCode, Integer orderCount) {
        //先查库存后减少
        Storage storage = storageMapper.findByCommodityCode(commodityCode);
        log.info("原来的库存是多少 :{}", JSONObject.toJSONString(storage));
        storage.setCount(storage.getCount() - orderCount);
        storageMapper.updateById(storage);
    }
 初始值 
Storage表的 count 为990
Order表有10条数据  

测试流程:请求新接口   5秒后请求原有接口   
先清空idea的日志

测试结果为 :
Storage表的 count 988
Order表共12条 新增两条数据   userId 1001 userId 1002
一切正常:
2:流程简单总结(Storage服务)
1:@GlobalTransactional First先进: 进行查询 修改 提交本地事务
2:无事务进  在First 提交完本地事务之后: 进行查询 修改 提交本地事务
3:@GlobalTransactional 进行二段提交  清除undo_log
3:假设问题 脏读(Storage服务)
在原调用接口修改 @GlobalTransactional 为 @Transactional

假如在步骤1 :进行查询 修改 之间  有其他操作(步骤2)修改了该数据呢

修改代码设置 GlobalTransactional调用的时候 查询结束后睡眠30秒
由于feign请求的默认超时时间为2000(毫秒) 先修改为10000(毫秒)
    @Override
    public void deductStorage(String commodityCode, Integer orderCount,String transactionalFlag) {
        //先查库存后减少
        Storage storage = storageMapper.findByCommodityCode(commodityCode);
        log.info(transactionalFlag + " : 睡眠前原来的库存是多少 :{}", JSONObject.toJSONString(storage));
        if("GlobalTransactional".equals(transactionalFlag)){
            //休眠5秒,期间去调用其他接口
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //只是用来打印
        Storage storageAfter = storageMapper.findByCommodityCode(commodityCode);
        log.info(transactionalFlag + " : 睡眠后原来的库存是多少 :{}", JSONObject.toJSONString(storageAfter));

        storage.setCount(storage.getCount() - orderCount);
        storageMapper.updateById(storage);
        log.info(transactionalFlag + " : 已经修改成功");
    }
# feign重复请求
feign:
  httpclient:
    connection-timeout: 10000
    connection-timer-repeat: 10000

# Feign负载均衡配置 配置全局超时时间 毫秒单位   根据业务酌情配置
ribbon:
  #请求连接的超时时间,默认时间为1秒
  ConnectTimeout: 10000
  #请求处理的超时时间
  ReadTimeout: 10000 
初始值 
Storage表的 count 为988
Order表有14条数据  

测试流程:请求新接口   2秒后请求原有接口    
先清空idea的日志

测试结果为 :
Storage表的 count 987
Order表共14条 新增两条数据   userId 1001 userId 1002

明显 Storage的数据不对 应该为986条 那么原因就是上述

1:@GlobalTransactional First先进: 进行查询  等待  988
2:无事务进  在First 查询完后: 进行查询 修改 提交本地事务 (此时已经修改)988->987
3:@GlobalTransactional 根据步骤1的结果 进行修改  本地提交 988->987
4:@GlobalTransactional  全局提交  988->987
4 :如何解决问题 脏读 (Storage服务)(最后无法解决)
独立事务 读取  全局事务(分布式事务)未提交的值

尝试1:原接口改回为 GlobalTransactional 注解 (问题未解决)

初始值 
Storage表的 count 为987
Order表有14条数据  

测试流程:请求新接口   2秒后请求原有接口    
先清空idea的日志

测试结果为 :
Storage表的 count 986
Order表共16条 新增两条数据   userId 1001 userId 1002

尝试2:原接口 GlobalTransactional 注解 改为@GlobalLock (问题未解决)

 初始值 
Storage表的 count 为986
Order表有16条数据  

测试流程:请求新接口   2秒后请求原有接口    
先清空idea的日志

测试结果为 :
Storage表的 count 985
Order表共18条 新增两条数据   userId 1001 userId 1002

尝试3:原接口与新接口 同时加上 GlobalTransactional 与 @GlobalLock (问题未解决)

测试结果为 :
Storage表的 count 984
Order表共20条 新增两条数据   userId 1001 userId 1002

 尝试4:原接口与新接口 同时加上 GlobalTransactional (问题未解决)
          同时被调用接口加上@GlobalLock 
          
测试结果为 :
Storage表的 count 983
Order表共22条 新增两条数据   userId 1001 userId 1002   

尝试6:原接口与新接口 同时加上 GlobalTransactional (问题未解决)
         storage服务查询sql 添加  FOR UPDATE
         SELECT *** FROM WHERE ***  FOR UPDATE

测试结果为 :
Storage表的 count 982
Order表共24条 新增两条数据   userId 1001 userId 1002   
5:脏读切换为脏写修改代码 (Provider服务 Storage服务)
1:原调用接口修改 @GlobalTransactional 为 @Transactional(或者无事务)
2:First调用接口不变 还是 @GlobalTransactional 
3:去除 storage服务的睡眠时间
4:修改代码使得 provider报错
5:先调用 First调用接口  后调用 原接口
6:假设问题 脏写读(Provider服务 Storage服务)
第4步的时候 @GlobalTransactional 不是提交 而是根据undo_log回滚
那会产生什么情况?

初始数据为 :
Storage表的 count 982
Order表共24条 新增两条数据   userId 1001 userId 1002  

测试流程:请求新接口   2秒后请求原有接口    
先清空idea的日志

测试完数据:
Storage表的 count 980
Order表共24条  没有新增

Storage分析:
原接口没有添加分布式事务 Provider服务报错回滚不影响Storage服务
First接口添加了分布式事务 但由于Storage服务undo_log日志 和原数据库内容已经不一致
导致根据Storage服务undo_log 日志 来回滚失败

该日志表明:Field not equals, name count, old value 981, new value 980

注意:由于该undo_log的存在 因此Storage 服务会不断在控制台打日志出来
7:流程简单总结(Provider服务 Storage服务)
1:均进入Storage服务进行  进行查询 修改 提交本地事务
2:均在进入 Provider服务进行等待
3:First 接口添加了分布式事务  存在 Storage 的二段提交日志
4:原调用接口 实际上已经完成 Storage的全部事务提交

5:First 接口 Provider接口报错  Provider根据根据Order表根据undo_log回滚
6:First 接口 Provider接口报错  Storage服务根据Storage表根据undo_log回滚
7: 根据 Storage的Storage表根据undo_log回滚 失败 
8:Storage表根据undo_log保留  需要进行手动处理
8:脏读和脏写总结
由于seata 的 AT 模式为两段提交模式
第一段:本地事务
第二段:全局事务(分布式事务)

分布式事务的undo_log在只是作用于在异常情况下 回滚 本地事务  
即是:本地事务 实际上已经提交  然后再被 分布式事务的undo_log 回滚而已

全局事务 的本地事务 读取 A=10
全局事务 的本地事务 提交 A+1 ——》 10+1=11
全局事务 最终提交

脏读:
    全局事务 的本地事务 读取 A=10
    独立事务(其他事务) 修改 A+1=11
    全局事务 的本地事务 提交 A+1 ——》 10+1=11
    全局事务 最终提交   
    
    最终 A=11

脏写:
    全局事务 的本地事务 读取 A=10
    全局事务 的本地事务 提交 A+1 ——》 10+1=11
    独立事务(其他事务)修改 A+1 ——》 11+1=12
    全局事务 中其他服务异常
    全局事务 的本地事务 操作:提交 A+1 ——》 10+1=11  回滚失败
   
   最终 A=12 并且  保留操作:提交 A+1 ——》 10+1=11 的undo_log

项目连接

请配合项目代码食用效果更佳:
项目地址:
https://github.com/hesuijin/spring-cloud-alibaba-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/spring-cloud-alibaba-project.git

https://github.com/hesuijin/spring-cloud-alibaba-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/spring-cloud-alibaba-project.git

在service-storage-demo 模块下   
在service-consumer-demo 模块下
在service-provider-demo 模块下   
在service-provider-demo-other 模块下    

相关文章

网友评论

      本文标题:微服务 17: Seata AT模式 并发测试(下)(文末有项目

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