記一次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要保證是相等的。再來談談這種方案的缺點吧,其實是挺明顯:進行的數據庫操作次數較多,不適合併發量大的情況。但是話又說回來了,一般訂單的訂單號(流水號)的生成方式不會按這樣的規則來,流水號的生成往往都是使用時間戳+隨機數的方式來生成,如果併發量很大,可以考慮將隨機數的位數設置得大一些,這樣就幾乎不會產生衝突了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章