国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > PHP > 正文

php結合redis實現高并發下的搶購、秒殺功能

2019-11-08 03:00:29
字體:
來源:轉載
供稿:網友

本文轉自http://blog.csdn.net/nuli888/article/details/51865401

搶購、秒殺是如今很常見的一個應用場景,主要需要解決的問題有兩個:

1 高并發對數據庫產生的壓力2 競爭狀態下如何解決庫存的正確減少("超賣"問題)對于第一個問題,已經很容易想到用緩存來處理搶購,避免直接操作數據庫,例如使用Redis。重點在于第二個問題

常規寫法:

查詢出對應商品的庫存,看是否大于0,然后執行生成訂單等操作,但是在判斷庫存是否大于0處,如果在高并發下就會有問題,導致庫存量出現負數

[php] view plain copy <?php  $conn=MySQL_connect("localhost","big","123456");    if(!$conn){        echo "connect failed";        exit;    }   mysql_select_db("big",$conn);   mysql_query("set names utf8");    $優化方案1:將庫存字段number字段設為unsigned,當庫存為0時,因為字段不能為負數,將會返回false

[php] view plain copy //庫存減少  $sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0";  $store_rs=mysql_query($sql,$conn);    if(mysql_affected_rows()){        insertLog('庫存減少成功');  }  

優化方案2:使用MySQL的事務,鎖住操作的行

[php] view plain copy <?php  $conn=mysql_connect("localhost","big","123456");    if(!$conn){        echo "connect failed";        exit;    }   mysql_select_db("big",$conn);   mysql_query("set names utf8");    $price=10;  $user_id=1;  $goods_id=1;  $sku_id=11;  $number=1;    //生成唯一訂單號  function build_order_no(){      return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);  }  //記錄日志  function insertLog($event,$type=0){      global $conn;      $sql="insert into ih_log(event,type)       values('$event','$type')";        mysql_query($sql,$conn);    }    //模擬下單操作  //庫存是否大于0  mysql_query("BEGIN");   //開始事務  $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此時這條記錄被鎖住,其它事務必須等待此次事務提交后才能執行  $rs=mysql_query($sql,$conn);  $row=mysql_fetch_assoc($rs);  if($row['number']>0){      //生成訂單       $order_sn=build_order_no();       $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)       values('$order_sn','$user_id','$goods_id','$sku_id','$price')";        $order_rs=mysql_query($sql,$conn);             //庫存減少      $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";      $store_rs=mysql_query($sql,$conn);        if(mysql_affected_rows()){            insertLog('庫存減少成功');          mysql_query("COMMIT");//事務提交即解鎖      }else{            insertLog('庫存減少失敗');      }  }else{      insertLog('庫存不夠');      mysql_query("ROLLBACK");  }  ?>  

優化方案3:使用非阻塞的文件排他鎖

[php] view plain copy <?php  $conn=mysql_connect("localhost","root","123456");    if(!$conn){        echo "connect failed";        exit;    }   mysql_select_db("big-bak",$conn);   mysql_query("set names utf8");    $price=10;  $user_id=1;  $goods_id=1;  $sku_id=11;  $number=1;    //生成唯一訂單號  function build_order_no(){      return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);  }  //記錄日志  function insertLog($event,$type=0){      global $conn;      $sql="insert into ih_log(event,type)       values('$event','$type')";        mysql_query($sql,$conn);    }    $fp = fopen("lock.txt", "w+");  if(!flock($fp,LOCK_EX | LOCK_NB)){      echo "系統繁忙,請稍后再試";      return;  }  //下單  $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";  $rs=mysql_query($sql,$conn);  $row=mysql_fetch_assoc($rs);  if($row['number']>0){//庫存是否大于0      //模擬下單操作       $order_sn=build_order_no();       $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)       values('$order_sn','$user_id','$goods_id','$sku_id','$price')";        $order_rs=mysql_query($sql,$conn);             //庫存減少      $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";      $store_rs=mysql_query($sql,$conn);        if(mysql_affected_rows()){            insertLog('庫存減少成功');          flock($fp,LOCK_UN);//釋放鎖      }else{            insertLog('庫存減少失敗');      }   }else{      insertLog('庫存不夠');  }  fclose($fp);  優化方案4:使用redis隊列,因為pop操作是原子的,即使有很多用戶同時到達,也是依次執行,推薦使用(mysql事務在高并發下性能下降很厲害,文件鎖的方式也是)

先將商品庫存如隊列

[php] view plain copy <?php  $store=1000;  $redis=new Redis();  $result=$redis->connect('127.0.0.1',6379);  $res=$redis->llen('goods_store');  echo $res;  $count=$store-$res;  for($i=0;$i<$count;$i++){      $redis->lpush('goods_store',1);  }  echo $redis->llen('goods_store');  ?>  搶購、描述邏輯[php] view plain copy <?php  $conn=mysql_connect("localhost","big","123456");    if(!$conn){        echo "connect failed";        exit;    }   mysql_select_db("big",$conn);   mysql_query("set names utf8");    $price=10;  $user_id=1;  $goods_id=1;  $sku_id=11;  $number=1;    //生成唯一訂單號  function build_order_no(){      return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);  }  //記錄日志  function insertLog($event,$type=0){      global $conn;      $sql="insert into ih_log(event,type)       values('$event','$type')";        mysql_query($sql,$conn);    }    //模擬下單操作  //下單前判斷redis隊列庫存量  $redis=new Redis();  $result=$redis->connect('127.0.0.1',6379);  $count=$redis->lpop('goods_store');  if(!$count){      insertLog('error:no store redis');      return;  }    //生成訂單    $order_sn=build_order_no();  $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)   values('$order_sn','$user_id','$goods_id','$sku_id','$price')";    $order_rs=mysql_query($sql,$conn);     //庫存減少  $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";  $store_rs=mysql_query($sql,$conn);    if(mysql_affected_rows()){        insertLog('庫存減少成功');  }else{        insertLog('庫存減少失敗');  }   模擬5000高并發測試webbench -c 5000 -t 60 http://192.168.1.198/big/index.phpab -r -n 6000 -c 5000  http://192.168.1.198/big/index.php

上述只是簡單模擬高并發下的搶購,真實場景要比這復雜很多,很多注意的地方如搶購頁面做成靜態的,通過Ajax調用接口再如上面的會導致一個用戶搶多個,思路:需要一個排隊隊列和搶購結果隊列及庫存隊列。高并發情況,先將用戶進入排隊隊列,用一個線程循環處理從排隊隊列取出一個用戶,判斷用戶是否已在搶購結果隊列,如果在,則已搶購,否則未搶購,庫存減1,寫數據庫,將用戶入結果隊列。

測試數據表

[php] view plain copy --  -- 數據庫: `big`  --    -- --------------------------------------------------------    --  -- 表的結構 `ih_goods`  --      CREATE TABLE IF NOT EXISTS `ih_goods` (    `goods_id` int(10) unsigned NOT NULL AUTO_INCREMENT,    `cat_id` int(11) NOT NULL,    `goods_name` varchar(255) NOT NULL,    PRIMARY KEY (`goods_id`)  ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;      --  -- 轉存表中的數據 `ih_goods`  --      INSERT INTO `ih_goods` (`goods_id`, `cat_id`, `goods_name`) VALUES  (1, 0, '小米手機');    -- --------------------------------------------------------    --  -- 表的結構 `ih_log`  --    CREATE TABLE IF NOT EXISTS `ih_log` (    `id` int(11) NOT NULL AUTO_INCREMENT,    `event` varchar(255) NOT NULL,    `type` tinyint(4) NOT NULL DEFAULT '0',    `addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,    PRIMARY KEY (`id`)  ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;    --  -- 轉存表中的數據 `ih_log`  --      -- --------------------------------------------------------    --  -- 表的結構 `ih_order`  --    CREATE TABLE IF NOT EXISTS `ih_order` (    `id` int(11) NOT NULL AUTO_INCREMENT,    `order_sn` char(32) NOT NULL,    `user_id` int(11) NOT NULL,    `status` int(11) NOT NULL DEFAULT '0',    `goods_id` int(11) NOT NULL DEFAULT '0',    `sku_id` int(11) NOT NULL DEFAULT '0',    `price` float NOT NULL,    `addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,    PRIMARY KEY (`id`)  ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表' AUTO_INCREMENT=1 ;    --  -- 轉存表中的數據 `ih_order`  --      -- --------------------------------------------------------    --  -- 表的結構 `ih_store`  --    CREATE TABLE IF NOT EXISTS `ih_store` (    `id` int(11) NOT NULL AUTO_INCREMENT,    `goods_id` int(11) NOT NULL,    `sku_id` int(10) unsigned NOT NULL DEFAULT '0',    `number` int(10) NOT NULL DEFAULT '0',    `freez` int(11) NOT NULL DEFAULT '0' COMMENT '虛擬庫存',    PRIMARY KEY (`id`)  ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='庫存' AUTO_INCREMENT=2 ;    --  -- 轉存表中的數據 `ih_store`  --    INSERT INTO `ih_store` (`id`, `goods_id`, `sku_id`, `number`, `freez`) VALUES  (1, 1, 11, 500, 0); 
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 华蓥市| 满城县| 囊谦县| 屏东县| 北碚区| 微博| 清涧县| 和林格尔县| 调兵山市| 体育| 田林县| 陆丰市| 台东县| 南开区| 宜宾县| 渭南市| 广宁县| 水富县| 南漳县| 宁都县| 林西县| 古交市| 佛坪县| 资源县| 舒城县| 澳门| 多伦县| 富锦市| 通海县| 大厂| 泊头市| 安西县| 阜新市| 嵊州市| 衡南县| 东山县| 吉木萨尔县| 紫云| 蒙山县| 克山县| 巨野县|