談談電商秒殺高併發的處理
眾所周知現在連市場賣菜的大媽都快知道高併發了,哈哈,那麼我們生活中是否接觸過高併發呢。當然了哈哈,比如你給你女朋友搶秒殺的化妝品什麼的了。秒殺最棘手的問題就是解決併發帶來的問題。下面我們一起聊聊嘍。
首先我們來說下問題:秒殺高併發帶來的最大問題,就是庫存超賣。(如果你沒看過我的文件,導致你寫公司秒殺業務時庫存超賣了,公司損失了,將你開除了,你會多麼不開心,哈哈我來給你寫稻草救救你)
嘻嘻嘻嘻嘻嘻嘻
解決方案:解決高併發導致庫存超賣問題的核心思想就是,庫存扣減要保證序列化操作。
1.資料庫層面解決方案:
這種方式實現起來最簡單,就是利用樂觀鎖。
update product set num=num-1 where num-1>0 and pid=#{pid}
使用此方法一定要注意,set num 時一定不要使用,set num=#{num},這樣做的話不能保證原子化操作,併發還是會有問題(覆蓋更新)。
缺點:資料庫存在瓶頸,而秒殺問題就是請求量大併發高。那怎麼辦?繼續看本大俠說咯。
2.使用快取(Redis等)解決方案:
使用快取確實可以解決資料庫瓶頸的問題,例如Redis天然的序列化操作(Redis單執行緒),並且在資料記憶體中做操作很快。
使用Redis的自增incr或自減decr操作庫存,判斷返回結果是否為0,如果為0表示秒殺失敗。這樣不就保證了庫存的不超賣。
此時你可能說,啊我明白了不就是用Redis的快取來搞嗎,簡單簡單。本大俠哈哈一笑,要是這樣的話本大俠不也去BAT了。
=================下面才是關鍵=========================================
說說我們目前滿意的Redis方案的問題吧。
先說一個優化的問題:
1.每次我們通過Redis查詢庫存都是通過遠端連線的方式,雖然很快,但是併發大的時候,這裡還是要優化一下的。
怎麼優化?簡單的說就是,當發現Redis庫存為0時,我們在程式中設定一個標識位,秒殺邏輯中每次進來先判斷標誌位。這樣庫存為0時就直接返回,而不用再遠端連線查詢Redis了。
你可能會說這樣就行了吧?對於一般的秒殺來說應該可以了,但我還要和大家說說更多的優化和優化時的問題,希望對大家的工作有好的提示或者幫助,那樣我會很高興的。哈哈
2.每次當我們秒殺成功後,會建立訂單、通知庫房、通知快遞等一系列操作,如果我們把這些操作也放在秒殺時來處理,那麼我們程式處理起來可想而知,會很慢的。那麼這時我們就要優化,怎麼優化?
實現非同步處理,我們可以通過MQ(ActivityMQ等訊息佇列)來實現非同步處理,當用戶秒殺成功後,我們傳送訊息給其他服務,然後返回給使用者秒殺結果,這樣是不是就很快了呢。對是快了。
那麼問題來了:使用者秒殺成功後需要付款,但是此時是非同步操作,佇列可能並沒有處理完訊息,怎麼辦怎麼辦?哈哈,這時我們需要在前端加一個輪詢,輪詢什麼?輪詢查詢秒殺的結果(象徵的意思意思使用者,排隊中什麼什麼的了)。下面程式碼
簡單講講這段程式碼:首先判斷登入就不說了,後面通過使用者名稱查詢下當前使用者是否秒殺成功了,然後然後問題來了,哈哈為什麼我下面要使用了資料庫查詢?而不是查詢快取中的商品數量是否為0,因為還是之前的問題,佇列沒有消化完,使用者秒殺成功的記錄還沒有生成,如果查詢資料庫商品沒有了,那就代表隊列已經處理完了,代表你秒殺失敗了GG。如果還有庫存,那麼返回success(0)告訴使用者排隊中。直到資料庫中庫存沒有了,那麼代表隊列處理完了,這時候你在查詢Redis應該訂單資訊的,如果沒有那麼你真的是秒殺失敗了。講到這裡可能大家理會的差不多了,但是這段程式碼中還是存在問題?什麼問題呢,那就是當他查詢Redis時佇列訊息還是沒處理她的訊息,當他查詢資料庫之前,佇列處理完了,這樣你查資料庫發現庫存沒有了,你就會返回秒殺失敗,但其實你是秒殺成功的。這樣還是會影響使用者體驗的。你可能會說不能吧,那麼巧。哈哈我就是要和你說高併發下這種情況出現很正常。所以呢,我們要防止,怎麼防止呢?
終極解決方案:
我們在查資料庫判斷庫存為0時,我們再次查詢Redis裡是否已經有生成的訂單了,這樣就避免了問題咯。這也就是江湖中傳聞的江湖祕訣,雙重校驗鎖,哈哈,其實關於秒殺啊高併發的問題還有很多,這類問題每一行程式碼都是要考慮很多情況的,希望我再這裡能給大家一個拋磚引玉的作用。本大俠要睡覺了,如果哪裡說得不對別噴啊,都同行哈哈哈。