庫存超賣的問題作描述:一般電子商務網站都會遇到如團購、秒殺、特價之類的活動,而這樣的活動有一個共同的特點就是訪問量激增、上千甚至上萬人搶購一個商品。然而,作為活動商品,庫存肯定是很有限的,如何控制庫存不讓出現超買,以防止造成不必要的損失是眾多電子商務網站程序員頭疼的問題,這同時也是最基本的問題。
從技術方面剖析,很多人肯定會想到事務,但是事務是控制庫存超賣的必要條件,但不是充分必要條件。
舉例:
總庫存:4個商品
請求人:a、1個商品 b、2個商品 c、3個商品
程序如下:
beginTranse(開啟事務) try{ $result = $dbca->query('select amount from s_store where postID = 12345'); if(result->amount > 0){ //quantity為請求減掉的庫存數量 $dbca->query('update s_store set amount = amount - quantity where postID = 12345'); } }catch($e Exception){ rollBack(回滾) } commit(提交事務)
以上代碼就是我們平時控制庫存寫的代碼了,大多數人都會這么寫,看似問題不大,其實隱藏着巨大的漏洞。數據庫的訪問其實就是對磁盤文件的訪問,數據庫中的表其實就是保存在磁盤上的一個個文件,甚至一個文件包含了多張表。例如由於高並發,當前有三個用戶a、b、c三個用戶進入到了這個事務中,這個時候會產生一個共享鎖,所以在select的時候,這三個用戶查到的庫存數量都是4個,同時還要注意,mysql innodb查到的結果是有版本控制的,再其他用戶更新沒有commit之前(也就是沒有產生新版本之前),當前用戶查到的結果依然是舊版本;
然后是update,假如這三個用戶同時到達update這里,這個時候update更新語句會把並發串行化,也就是給同時到達這里的是三個用戶排個序,一個一個執行,並生成排他鎖,在當前這個update語句commit之前,其他用戶等待執行,commit后,生成新的版本;這樣執行完后,庫存肯定為負數了。但是根據以上描述,我們修改一下代碼就不會出現超買現象了,代碼如下:
beginTranse(開啟事務) try{ //quantity為請求減掉的庫存數量 $dbca->query('update s_store set amount = amount - quantity where postID = 12345'); $result = $dbca->query('select amount from s_store where postID = 12345'); if(result->amount < 0){ throw new Exception('庫存不足'); } }catch($e Exception){ rollBack(回滾) } commit(提交事務)
就是在update時,代入之前的庫存作為條件,如:
update stocks set count=count-1 where count=20 and productid=xxxx
這樣,在並發時,如果存在多條同時update,那么只有一條是成功的,其他的不會寫入數據,也不需要回滾,只需要獲得是否update成功的信息,就轉去相應的處理界面就可以了。
1.用額外的單進程處理一個隊列,下單請求放到隊列里,一個個處理,就不會有並發的問題了,但是要額外的后台進程以及延遲問題,不予考慮。
2.數據庫樂觀鎖,大致的意思是先查詢庫存,然后立馬將庫存+1,然后訂單生成后,在更新庫存前再查詢一次庫存,看看跟預期的庫存數量是否保持一致,不一致就回滾,提示用戶庫存不足。
3.根據update結果來判斷,我們可以在sql2的時候加一個判斷條件update ... where 庫存>0,如果返回false,則說明庫存不足,並回滾事務。
4.借助文件排他鎖,在處理下單請求的時候,用flock鎖定一個文件,如果鎖定失敗說明有其他訂單正在處理,此時要么等待要么直接提示用戶"服務器繁忙"
本文要說的是第4種方案,大致代碼如下:
redis 127.0.0.1:6379> set number 109 OK redis 127.0.0.1:6379> get number "109" redis 127.0.0.1:6379> INCRBY number 20 (integer) 129 redis 127.0.0.1:6379> INCRBY number 20 (integer) 149 redis 127.0.0.1:6379> INCRBY number 21 (integer) 170 redis 127.0.0.1:6379> INCRBY number -21 (integer) 149 redis 127.0.0.1:6379>
這樣當number小於等於0的時候,就說明庫存沒有了;
不知道這種方式有沒有問題,沒有具體實踐過,如果有知道的朋友可以留言交流一下!!!