1. 程式人生 > >避免商品超賣的4種方案

避免商品超賣的4種方案

加鎖 行操作 pda p s 方式 數據庫連接數 發現 commit pan

原始方案(失敗):在每次下訂單前我們判斷促銷商品的數量夠不夠,不夠不允許下訂單,更改庫存量時加上一個條件,只更改商品庫存大於0的商品的庫存,當時我們使用ab進行壓力測試,當並發超過500,訪問量超過2000時,還是會出現超賣現象。

public function buyOne() 
{
    $shop = Shop::find(1);
    if ($shop->number > 0) {
        DB::update("update shop set number = number - 1 where id = 1");
    }
}

第1種方案:使用mysql的事務加排他鎖來解決,首先我們選擇數據庫的存儲引擎為innoDB,使用的是排他鎖實現的,剛開始的時候我們測試了下共享鎖,發現還是會出現超賣的現象。有個問題是,當我們進行高並發測試時,對數據庫的性能影響很大,導致數據庫的壓力很大。

//2.利用數據庫的forupdate來加鎖(在數量少的情況下並不會出現問題,但是當並發達到(ab -n 1000 -c 200),
//就會出現請求非2XX的響應增多,1000 失敗了 60)time per request 65.195
//在高並發的情況下,會導致數據庫連接數不夠,部分php獲取不到連接而報錯,或者是超過等待時間而報錯

public function indexMysql() 
{   DB
::beginTransaction();   //通過for update 加排它鎖   $shop = DB::table(‘shop‘)->where(‘id‘, ‘=‘, 1)->lockForUpdate()->first();   
if ($shop->number > 0) {     if (DB::update("update shop set number = number - 1 where id = 1")) {       DB::commit();     } else {       DB::rollBack();//回滾並重試       usleep(100000);       $this->indexMysql();     }   } else {     DB::commit();   } }

第2種方案:使用文件鎖實現。當用戶搶到一件促銷商品後先觸發文件鎖,防止其他用戶進入,該用戶搶到促銷品後再解開文件鎖,放其他用戶進行操作。這樣可以解決超賣的問題,但是會導致文件得I/O開銷很大。

第3種方案:使用redis的setnx來實現鎖機制。但是並發大的情況下,鎖的爭奪會變多,導致響應越來越慢。(與第四種方案類似)

//在數量少的情況下並不會出現問題,但是當並發達到(ab -n 1000 -c 200 就會出現請求非2XX的響應增多,1000 失敗了 54) time per request 127.575

public function index() 
{   
//測試並發超賣現象   if (Redis::setnx(self::KEY, 1)) {//拿到了鎖     $this->buy();   } else {     usleep(100000);//等會再去拿鎖     //Log::info("未爭奪到鎖,睡眠100ms");     $this->index();   } }
private function buy() 
{   
$shop = Shop::find(1);   if ($shop->number > 0) {     $shop->number --;     $shop->save();   }   Redis::del(self::KEY); }

第4種方案:redis的隊列來實現。將要促銷的商品數量以隊列的方式存入redis中,每當用戶搶到一件促銷商品則從隊列中刪除一個數據,確保商品不會超賣。這個操作起來很方便,而且效率極高

//4.使用redis隊列來,用戶過來直接入隊列,然後再將操作更新到數據庫
//最佳體驗(redis pconnect 9.481s, 無丟失, 無框架)

public function push() 
{   
//入隊列   Redis::lpush(self::QUEUE, 1); }

//腳本調用pop方法 * * * * * php xxx.php

public function pop() 
{
    while (($key = Redis::rpop(self::QUEUE))) {
      $shop = Shop::find(1);
      if ($shop->number > 0) {
        DB::update("update shop set number = number - 1 where id =         1")
     }
  }
}

避免商品超賣的4種方案