调用外部api时的数据一致性问题

春节又要来了,远行的小伙伴们将开始一场刺激的抢票之旅,关于购票,从程序角度上而言,大致分为这么几步:

  

 1、 检查是否有剩余的票

 2、 购票后票数减一

 

 3、 账户上扣除金额

 4、 获得火车票

如果执行顺利,一切ok,如果中途执行出现异常,比如扣除金额的时候出现异常,你账户上的金额未减,也没有获得火车票,但剩余票数却莫名地少了一张,这就是我们常说的事务的一致性问题,是由于数据库运行中途发生故障,导致数据库中的状态部分改变,使数据出现不一致的情况。

事务的一致性需要由原子性来保证,即对于一系列操作,要么全部成功,要么全部失败回滚,以上述例子而言,账户金额扣除发生异常时,之前的写操作就要全部回滚,恢复到执行前的状态,这个大部分数据库都提供支持,我们平时只需要借助spring的aop机制,声明事务即可。

数据库事务与隔离级别

全面分析 Spring 的编程式事务管理及声明式事务管理

ThreadLocal与Spring 事务管理

然而,并不是每一步操作都可以借助数据库的事务机制保持数据一致性的,有时候我们常常要调用开放平台的api接口,比如一些第三方的卖家管理软件有时候会帮助淘宝卖家进行一些自动上下架的操作,这些操作全部是通过定时调用淘宝开放给开发者的自动上下架api进行的,因为后续有新的待操作商品加入,所以调用会每隔几个小时进行一次,调用返回正确结果后再修改本地数据库相关的状态,一个比较的直观的过程是这样:

  

     // 该方法开启事务  传播属性为REQUIRED

   public void shelveOperation(){

        //……other db operation

     

       1 boolean  status = ApiUtils.autoShelve();

           …….

       2  Operation ope = new Operation();

           ope.setStatus(status);//status 为操作结果,成功或失败

           …….

       3 opeDao.insert(ope);

  }

以上方法配置了事务,假如2与3步骤之间执行的时候抛出一个异常,所有之前针对数据库的操作都会回滚,但是1步骤却不会回滚,上下架请求已经发送给了淘宝平台,平台已经进行了相应的操作并且返回状态,如果同样对平台的操作作一番回滚,那是一种资源的浪费,而且平台一般会限制这样的操作。

开发一个系统让他能够在常规状况下运行是要花费很多时间和精力的,开发一个健壮的系统使他能够应对各种异常情况,发生错误后我们能够很快定位解决问题,手动乃至自动恢复到正常运行的状态,则需要更细致的思考。

对于以上问题,有一个解决思路是再编写一个定时任务,对于一些失败的状态重新执行,但是由于回滚,最后的失败状态都没记录下来,程序再次定时执行的时候,从本地数据库里获取的状态就会产生误导作用,好像之前从未进行过操作似的,最终导致对请求的重复操作。当然我们可以通过log日志排查解决这些问题,但其自动化和实时性程度毕竟不够。

因此,在开始调用平台的接口之前,可以再执行一个原子性操作:

   // 该方法开启事务 传播属性为REQUIRED

public  void shelveJob(){

   boolean result= obj.startShelve();

   if(result){

      obj.shelveOperation();

    }

}

   // 该方法开启事务,传播属性为REQUIRES_NEW

   public void startShelve(){

         //……other db operation

         Operation ope = new Operation();

         …….

         ope.setStatus(status);// status 为“开始操作”

         opeDao.insert(ope);

         

 }

shelveOperation方法改为:

   // 该方法开启事务 REQUIRED

   public void shelveOperation(){

        //……other db operation

     

       1 boolean  status = ApiUtils.autoShelve();

           …….

       2  Operation ope = new Operation();

            ……

           ope.setStatus(status);//status 为操作结果,成功或失败

       3 opeDao.update(ope); //新增变成更新操作

 }

startShelve()方法主要是做一些准备操作,该新增的记录则新增,并且将相关记录的状态变成“开始操作”,下次如果shelveOperation()执行失败,数据回滚,startShelve()的执行结果依然保存。标注为开启事务的方法传播机制默认为“REQUIRED”,而startShelve()方法则为REQUIRES_NEW。这是为了将它和当前事务独立开来,使startShelve()执行完毕后提交插入的记录,而不被外层调用方法的回滚影响。下面简单罗列下事务的传播属性:

REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。

MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。

PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常

NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

定时检查任务会对状态处于“开始操作”或者“失败”的记录状态进行再执行

public  void shelveOpeRecoveryJob(){

    shelveOpeRecovery();

}

   // 该方法开启事务

   public void shelveOpeRecovery(){

       //……get the operation records

      for(遍历处于开始状态或者失败状态的记录){

          if(状态为开始){
             //调用平台接口,检查该操作记录真实状态

              boolean  result= ApiUtils.isOperated(ope.getNO());//该记录是否已经做过处理

              if(result){

                 //不访问api,直接更新本地状态

             }else{

                // 访问api后更新本地状态

              }

          }else if(状态为失败){

               // 访问api后更新本地状态

          }

 }

在编写业务逻辑代码时,常常会想到一个二八定律例子,某段代码仅用了百分之二十的时间编写测试完成,业务百分之八十的情况可以正常运行,但剩余百分之二十的特殊情况,异常问题,却常常需要百分之八十的时间去完善,如果前期考虑不周密,到后期在生产环境以bug的形式表现出来,可能需要更多的时间。

关于事务的其他不错的资料:

Spring五个事务隔离级别和七个事务传播行为

http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt216

深入理解 Spring 事务原理

http://www.codeceo.com/article/spring-transactions.html


解惑 spring 嵌套事务

http://www.blogjava.net/baoyaer/articles/248203.html

Spring事务处理的实现

http://blog.csdn.net/chjttony/article/details/6528344

java达人

ID:java_daren

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值