1.
一般在商城中,我們經常遇到某件商品隻限10人搶購、秒殺
if($num > 0){
//使用者搶購成功,記錄使用者資訊
$num--;
}
假設在一個并發量較高的場景,資料庫中num的值為1時,可能同時會有多個程序讀取到num為1,程式判斷符合條件,搶購成功,num減一。這樣會導緻商品超發的情況,本來隻有10件可以搶購的商品,可能會有超過10個人搶到,此時num在搶購完成之後為負值。
我們該怎麼解決呢?
這裡我們可以有兩種方案,基于MySQL和redis的方案。
基于MySQL(悲觀鎖和樂觀鎖)
1、悲觀鎖
悲觀鎖的方案采用的是排他讀,也就是同時隻能有一個程序讀取到num的值。事務在送出或復原之後,鎖會釋放,其他的程序才能讀取(SELECT … FOR UPDATE)
2、樂觀鎖
樂觀鎖的方案在讀取資料是并沒有加排他鎖,而是通過一個每次更新都會自增的version字段來解決,多個程序讀取到相同num,然後都能更新成功的問題。在每個程序讀取num的同時,也讀取version的值,并且在更新num的同時也更新version,并在更新時加上對version的等值判斷。假設有10個程序都讀取到了num的值為1,version值為9,則這10個程序執行的更新語句都是UPDATE goods SET num=num-1,version=version+1 WHERE version=9,然而當其中一個程序執行成功之後,資料庫中version的值就會變為10,剩餘的9個程序都不會執行成功,這樣保證了商品不會超發,num的值不會小于0,但這也導緻了一個問題,那就是發出搶購請求較早的使用者可能搶不到,反而被後來的請求搶到了。
基于redis解決方案
1、基于watch的樂觀鎖方案
watch用于監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他指令所改動,那麼事務将被打斷。這種方案跟mysql中的樂觀鎖方案類似,具體表現也是一樣的。
2、基于list的隊列方案
基于隊列的方案利用了redis出隊操作的原子性,搶購開始之前首先将商品編号放入響應的隊列中,在搶購時依次從隊列中彈出操作,這樣可以保證每個商品隻能被一個程序擷取并操作,不存在超發的情況。該方案的優點是了解和實作起來都比較簡單,缺點是當商品數量較多是,需要将大量的資料存入到隊列中,并且不同的商品需要存入到不同的消息隊列中