在進行電商平台開發的時候,我們必定需要考慮到商品的超賣現象或者是秒殺模組功能的實現,因此不得不考慮到高併發所帶來的問題,綜合網上的各類部落格文章,我對商品超賣這樣的問題處理進行了從淺到深的逐步分析。(秒殺和搶購功能類似)
我們先從原先的方法來談論可能會出現什麼問題?
原始方法就是直接查詢請求是否符合庫存數量,符合則進行下單處理,但是由於資料庫的讀操作是 無鎖查詢 ,也就是說,在同一時間如果有多個使用者進行查詢那麼他們得到的庫存資料是一樣的,都能夠進行下單操作,這樣必然就出現了超賣現象。
那麼怎麼處理?首先我們勢必想到的給要修改的資料庫加鎖,這裡我們就要引入鎖的概念。
先說說有什麼鎖我們可以用到:悲觀鎖和樂觀鎖
悲觀鎖:每次拿資料的時候都認為別的執行緒會修改資料,所以在每次拿的時候都會給資料上鎖。上鎖之後,當別的執行緒想要拿資料時,就會阻塞,直到給資料上鎖的執行緒將事務提交或者回滾。 其中悲觀鎖,包含了行鎖、表鎖、共享鎖、排他鎖等。
樂觀鎖:預設別的執行緒不會修改資料,所以不會上鎖。常用的判斷實現方式是使用版本戳,例如在一張表中新增乙個整型欄位version,每更新
version++
,比如某個時刻
version=1
,執行緒a
讀取了此
version=1
,執行緒b
也讀取了此
version=1
,當執行緒
a更新資料之前,判斷
version
仍然為1
,更新成功,
version++變為2
,但是當執行緒
b再提交更新時,發現
version變為2
了,與之前讀的
version=1
不一致,就知道有別的執行緒更新了資料,這個時候就會進行業務邏輯的處理。
通常情況下,寫操作較少時,使用樂觀鎖,寫操作較多時,使用悲觀鎖。
共享鎖:共享鎖又稱為讀鎖,乙個執行緒給資料加上共享鎖後,其他執行緒只能讀資料,不能修改。
如何加共享鎖?可以使用
select ... lock in share mode
語句。
排他鎖:排他鎖又稱為寫鎖,和共享鎖的區別在於,其他執行緒既不能讀也不能修改。 如何 加排他鎖 ? 可以使用select ...for update 語句 ,所以行鎖和表鎖就是排他鎖。
行鎖: 在事務中
執行緒a 通過select name student where id=1 from for update語句給 id為
1的資料行上了鎖。 那麼其他 執行緒此時可以使用select語句讀取資料,但是如果也使用
select for update
語句 加鎖,或者 使用update,
add,
delete 都會 阻塞 ,直到 執行緒 a 將事務提交(或者回滾), 其他 執行緒
獲取 到 鎖 。
表鎖:當沒有查詢條件where的時候,如這條
sql所示
select * from student for update;
那麼就會形成表鎖。
使用mysql的事務加排他鎖解決, 前提必須保證 資料庫 表設計的 儲存引擎為innodb
核心**如下:
public function indexmysql() else
} else
} 結果:利用資料庫的for update來加鎖,在數量少的情況下並不會出現問題,但是當併發達到(
ab -n 1000 -c 200
),就會出現請求非
2xx的響應增多,因此在高併發的情況下,會導致資料庫連線數不夠,部分
php獲取不到連線而報錯,或者是超過等待時間而報錯。
缺點:當減庫存和高併發碰到一起的時候,由於操作的庫存數目在同一行,就會出現爭搶innodb行鎖的問題,導致出現互相等待甚至死鎖的情況,從而大大降低
mysql
的處理效能,最終導致前端頁面出現超時異常。
使用資料庫事務和資料庫的鎖機制,當高併發的時候,勢必導致資料庫連線數增大,這樣高頻率讀寫資料庫很容易導致資料庫宕機,因此, 並不建議在資料庫層面進行加鎖,反而建議通過服務端的記憶體鎖(鎖主鍵)來對該問題進行解決 ,那麼快取我們有什麼方法能夠實現防止商品超賣的情況呢?
下面介紹redis的兩種方法:分布式鎖、佇列序列化
分布式鎖的實現原理:
同乙個鎖key,同一時間只能有乙個客戶端拿到鎖,其他客戶端會陷入無限的等待來嘗試獲取那個鎖,只有獲取到鎖的客戶端才能執行下面的業務邏輯。
案例解析:
從上圖可以看到,只有乙個訂單系統例項可以成功加分布式鎖,然後只有他乙個例項可以查庫存、判斷庫存是否充足、下單扣減庫存,接著釋放鎖。釋放鎖之後,另外乙個訂單系統例項才能加鎖,接著查庫存,一下發現庫存只有2臺了,庫存不足,無法購買,下單失敗。不會將庫存扣減為
-8的。
缺點:併發大的情況下,鎖的爭奪會變多,導致響應越來越慢,假設加鎖之後到釋放鎖之前,的一系列操作「查庫存 ->
建立訂單
->
扣減庫存」全過程消耗
20毫秒,那同乙個商品在多使用者同時下單的情況下,會基於分布式鎖序列化處理,導致沒法同時處理同乙個商品的大量下單的請求。
如何對分布式鎖進行優化:使用分段加鎖技術方案,把資料分成很多個段,每個段是乙個單獨的鎖,所以多個執行緒過來併發修改資料的時候,可以併發的修改不同段的資料。假設場景:假如你現在iphone有
1000
個庫存,那麼你完全可以給拆成
20個庫存段,在資料庫的表裡建
20個庫存字段,比如
stock_01
,stock_02
,類似這樣的,也可以在
redis
之類的地方放
20個庫存
key。然後寫乙個簡單的隨機演算法,每個請求都是隨機在
20個分段庫存裡,選擇乙個進行加鎖。這樣每次就能夠處理20
將要**的商品數量以佇列的方式存入redis中,每當使用者搶到一件**商品則從佇列中刪除乙個資料,確保商品不會超賣。
**思路:
//1.當增加商品或修改商品庫存時
,將庫存資料存入快取
//2.如果使用者下單
,則在快取的該商品庫存
key進行刪除操作
(判斷刪除後值不小於0)
//3.通過快取獲取商品庫存資料顯示在前端
//3.邏輯判斷
,當庫存等於0時
,資料持久化操作
,並對商品下架處理
電商超賣現象的解決思路
在進行電商平台開發的時候,我們必定需要考慮到商品的超賣現象或者是秒殺模組功能的實現,因此不得不考慮到高併發所帶來的問題,綜合網上的各類部落格文章,我對商品超賣這樣的問題處理進行了從淺到深的逐步分析。秒殺和搶購功能類似 我們先從原先的方法來談論可能會出現什麼問題?原始方法就是直接查詢請求是否符合庫存數...
電商超賣問題
本專案的超賣類似於電商的秒殺超賣現象 1.不同使用者在讀請求的時候,發現商品庫存足夠,然後同時發起請求,進行秒殺操作,減庫存,導致庫存減為負數。2.同乙個使用者在有庫存的時候,連續發出多個請求,兩個請求同時存在,於是生成多個訂單。對於第一種超賣現象 1 最簡單的方法,更新資料庫減庫存的時候,進行庫存...
超賣現象及解決
本專案的超賣類似於電商的秒殺超賣現象 1.不同使用者在讀請求的時候,發現商品庫存足夠,然後同時發起請求,進行秒殺操作,減庫存,導致庫存減為負數。2.同乙個使用者在有庫存的時候,連續發出多個請求,兩個請求同時存在,於是生成多個訂單。對於第一種超賣現象 1 最簡單的方法,更新資料庫減庫存的時候,進行庫存...