背景
在公司里面我負責的是積分商城一塊,里面的積分商品也跟其它商品一樣,超賣是絕對不可以的。。。。
剛接手到積分商城
我剛來的時候,積分商城已經有了自家優惠券的功能,整個商城就2件商品:滿5減1+滿10減2.
我要做的第一個功能就是添加新的功能:第三方優惠券(其實就是跟我餓了么什么的一樣啦)。自家的優惠券是通過模版生成的,給用戶一個兌換碼;第三方優惠券是從數據庫里拿的(第三方給的),同樣是給用戶一個兌換碼。
具體實現的思路就是:找到這個商品,然后通過這個商品的某個字段來找到對應的兌換碼,這里的兌換碼會有很多,那么給他一張就行了。發兌換碼肯定是分為2步的:1.從庫里面取一張,2.將取出的這一張狀態標記為已使用。這里會出現一個問題,並發的情況下,也就是1.2步之間如果打個斷點,那么2個人會得到相同的一張兌換碼,而且第二次update的時候是對數據庫沒有影響的。
這里的解決方法就有很多了:
1.sql不怕累的:來個存儲過程;
2.java不怕累的:根據update返回的結果是1還是0(有沒有有效的update),來各種判斷各種處理;
3.不怕慢的:把2個操作放在同一個事務里面,,其實我最早的想法就是這樣的,因為以前做項目沒啥效率要求,我也一直覺得這樣很好。但是我們公司的讀寫是分離的,從代碼里面就能看到讀的slave是不帶事務的,至於他們使用的是否為同一個數據庫,還得看生產環境上怎么配。當時我就想把這里讀的那個也換成master來,然后加個事務,這樣基本就可以了。但是帶我的那個非常不滿意,表示如果把讀加上了事務會非常的慢,所以被否定了。。。現在想想也是有些道理,這個能否實現還得看事務隔離級別的設置,而隔離級別要是滿足了說不定運行起來還真的阻塞的哭了。。
4.最終的實現方案:我們最后用的是redis,所有的優惠券統一從redis中取,如果redis中沒有,那就一次性從數據庫中拿出5條來。使用了這個方法,讓我對redis的了解多了不少。后面商品修改的時候,由於redis中的緩存也讓我吃了不少苦---就是找不到問題所在,最后發現原來問題出在緩存沒清。。。
小結
第三方優惠券的處理方案:把優惠券放倒緩存中,然后統一從緩存中取。
后來商城的搶購活動
第三方提供了10件襯衫,要求我們搞個搶購。
當時給的時間也不多,也沒准備改啥代碼,就插了10條數據到第三方兌換券的數據庫,把襯衫當做第三方優惠券來看,然后最后哪10個人搶到了再從兌換記錄里面找。
之前用redis處理了並發的問題,所以對於這個搶購還是比較自信的。。最終也是沒有出現問題。
v3.0要求添加抽獎功能
也就是后天要上線的新功能,這兩天可把我給忙壞了;當時聽到要添加抽獎功能的消息時,可把我給高興壞了,可有好玩的東西寫了。
當時腦袋里浮現出來的就是各種貴重物品抽抽抽,產品也說到時候抽iphone啥的。
這個可千萬要小心,抽獎這事可別抽1個iphone搞出10個同時中獎來。。。
我的想法時這樣的:
第三方券能夠通過實實在在存在的券來控制,而其它的東西,沒有實際存在的券,該怎么實現並發不出問題呢?
第一個想法還是用緩存,這次把商品的剩余數量給放到redis中來,把分布式的並發交給redis來處理,每次get and 減一 嘛。
這個功能redis里好像是有,但是在spring-data的redis封裝里面我沒找到對應的方法。
於是我就想去找找以前的自家優惠券是怎么寫的,怎么控制不超買的,因為這個通過模版生成的東西也是通過商品的剩余數量來控制的,也並沒有實際存在的券。
代碼看完了,也沒發現哪里控制了並發,更沒發現哪里用到了緩存。
喜出望外,為啥?因為我發現了帶我的人代碼的問題,終於可以修復一下他的代碼來展現一下自己了。
於是我去網上搜有關 搶購超賣 的處理方案,基本上就是
1.緩存,就是我上面說的
2.消息隊列,應該就是序列化吧,別搶,都給我排隊去
3.存儲過程,我都懶得看。因為我知道我看明白了我也懶得寫那么長的sql的
4.update table set a=a-1 where a>=1 and id =x ,行鎖
這里我選擇了第4中方法,因為帶我的人說,不需要用緩存,使用sql就能完成,有行鎖。
網上的說法是,請求量很大的時候行鎖會使數據庫壓力非常大。我也覺得很有道理,不過想想我們積分商城的訪問量,就先用這個簡單的把,哪天訪問量吃不消的,讓我改成緩存我也樂意。
一頓改
翻了一下原來的代碼,發現並發是肯定會出問題的,為此我還測試了下,打了個斷點,的確出問題了。
之前的sql是這樣寫的:
update table set a=a-1 where id =x
然后剩余數量這字段還是unsigned的,所以報了out of range的錯,這明顯就是要超賣,而數據庫缺報錯了,不過2個用戶都提示兌換成功了。。
sql改了后,還是有問題,為啥,因為返回值沒用上啊。
之前的剩余數量可以說就是用來標記一下還有多少個,用來平常用用的。
現在我把它提到了方法的第一行,判斷返回值是否為0,多了一個處理並發的功能。
小結
簡單處理並發:
update table set a=a-1 where a>=1 and id =x
根據返回結果來看,如果為0,表示賣完了。不為0再繼續下面的操作