搶購場景完全靠數據庫來扛,壓力是非常大的,我們在最近的一次搶購活動改版中,采用了redis隊列+mysql事務控制的方案,畫了個簡單的流程圖:
先來就庫存超賣的問題作描述:一般電子商務網站都會遇到如團購、秒殺、特價之類的活動,而這樣的活動有一個共同的特點就是訪問量激增、上千甚至上萬人搶購一個商品。然而,作為活動商品,庫存肯定是很有限的,如何控制庫存不讓出現超買,以防止造成不必要的損失是眾多電子商務網站程序員頭疼的問題,這同時也是最基本的問題。
從技術方面剖析,很多人肯定會想到事務,但是事務是控制庫存超賣的必要條件,但不是充分必要條件。
舉例:
總庫存: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(提交事務)