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