zoukankan      html  css  js  c++  java
  • 记一次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要保证是相等的。再来谈谈这种方案的缺点吧,其实是挺明显:进行的数据库操作次数较多,不适合并发量大的情况。但是话又说回来了,一般订单的订单号(流水号)的生成方式不会按这样的规则来,流水号的生成往往都是使用时间戳+随机数的方式来生成,如果并发量很大,可以考虑将随机数的位数设置得大一些,这样就几乎不会产生冲突了。

  • 相关阅读:
    MyBatis学习存档(3)——mapper.xml映射文件
    Springboot Idea热部署以及重启后代码也不生效的问题解决
    属性值为空不更新到数据库工具类
    Poi工具类快速生成Ecxel
    Nginx配置ssl,实现https访问
    商城多商品组合数据格式
    Nginx泛解析配置
    Java支付宝支付接入流程,wap,app接入方式
    springboot配置@ResponseBody注解依然返回xml格式的数据
    通过aop记录日志,记录修改前后的数据,精确到每个字段
  • 原文地址:https://www.cnblogs.com/kuangdw/p/12811570.html
Copyright © 2011-2022 走看看