记一次CAS思想在实际项目中的应用

Posted kuangdw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次CAS思想在实际项目中的应用相关的知识,希望对你有一定的参考价值。

记CAS思想在实际开发中的一次应用

可能我们大多数人都懂CAS的原理,但是在实际开发中却是比较少真正用到它。本人在一次实际开发中还就真用到了,但是最后采用的解决方案感觉还不是最好的。下面就分享一下我遇到的问题和采用的解决方案吧,希望对遇到同样问题的朋友起到一点点灵感启发。


一、CAS原理简介

CAS(compare and set)其实是一种乐观锁的思想,个人理解来看,感觉可以将其大体上可以分为三步:
(1)从数据库中拿到要更改的数据,这里就记为oldValue吧,然后令期望值expectedValue=oldValue
(2)修改值:newVlue=oldValue+100
(3)更新值:再从数据库中拿到要更新的那个数据,这里就叫做当前oldValue,如果当前oldvalue等于expectedValue,就set oldValue=newValue并返回
否则循环(1)(2)直到当前oldValue等于expectedValue,然后再set oldValue=newValue并返回。


二、问题描述

在一次实际项目中碰到这么个问题:
首先,有一个订单表order,然后订单号的生成规则为:订单号 = 当前日期 + 0000 + 0001,如今天的第一笔订单的订单号为2020043000000001。然后后面的订单号单调递增,注意,order表中的订单号是唯一的。对于这个问题,我们可能首先想到的解决方法是使用redis+设置过期时间来解决,即每天凌晨产生当天的第一个订单号如2020043000000000,然后根据新产生的订单依次递增订单号。这种方法很容易想到,但是个人感觉redis中的订单号与数据库中order表中的订单号的一致性不是很好控制,所以没有采用这种方案。


三、个人采用的解决方案

(1)创建两张表,订单表order,还有生成订单号的表produce_orderid;
(2)order表用来存储订单信息,produce_orderid表只有有两个字段:orderdate,maxorderid。orderdate代表当期日期,maxorderid为当天最大订单号;
(3)所以根据个人在(一)中简述的CAS原理,就知道如何用代码解决了这个问题了;

这里附上新增订单方法的伪代码吧:

@Transactional //两个表中的操作是一个事务
public Result addOrder(Order order){
   1、if select from order where orderId = order.getOrderId()  == null?  成立则return 订单已存在,否则往下走
   2、if select from produce_orderid == null ?    
      成立则:insert into produce_orderid values(2020-04-30,2020043000000001);
             insert into order values(2020043000000001,订单的其他信息...);
             没有发生异常则返回成功,否则事务回滚返回失败
      否则往下走
   3、重点步骤:
      long oldvalue = select maxorderid from produce_orderid where orderdate =  Date;
      long newvalue = oldvalue + 1;
      flag = update produce_orderid set maxorderid = newvalue where orderdate =  Date and maxorderid=oldvalue;
      if(flag)  {   //如果更新成功说明没被他人改动,那么就在order表中新增订单并返回
           insert into order values(newvalue,其他信息);
           没有发生异常则返回成功,否则事务回滚返回失败;
          }
      //否则就说明被改动了,那么就进入循环
      while(flag != true){  //循环直到更新成功退出
           //将旧值+1,然后新值总比旧值大1
           oldvalue ++;
           newvalue = oldvalue + 1;
           flag = (update produce_orderid set maxorderid=newvalue where orderdate=Date and maxorderid=oldvalue;
         }
      //结束循环说明更新成功,就可以在order表中新增订单了
      insert into order values(newvalue,其他信息);
      没有发生异常则返回成功,否则事务回滚返回失败;
}

四、总结

解决方案的大致思想都在上面描述出来了,实际代码只需要将SQL语句转化成响应的业务逻辑就行了。总之,就是使用CAS的思想和事务控制来保证在多线程新增订单的情况下:1)不会有重复的订单号,2)order表中一天当中的最新的订单号与produce_orderid表中的当期日期的maxorderid要保证是相等的。再来谈谈这种方案的缺点吧,其实是挺明显:进行的数据库操作次数较多,不适合并发量大的情况。但是话又说回来了,一般订单的订单号(流水号)的生成方式不会按这样的规则来,流水号的生成往往都是使用时间戳+随机数的方式来生成,如果并发量很大,可以考虑将随机数的位数设置得大一些,这样就几乎不会产生冲突了。








以上是关于记一次CAS思想在实际项目中的应用的主要内容,如果未能解决你的问题,请参考以下文章

记一次java内存溢出的解决过程

记一次删除Git记录中的大文件的过程

记一次后端性能测试与调试

记一次后端性能测试与调试

记一次分布式锁使用不当引发生产事故.....

记一次MD5妙用