這次帶給大家php處理搶購類高並發請求實現詳解,php處理搶購類高並發請求的注意事項有哪些,下面就是實戰案例,一起來看一下。
本文以搶購、秒殺為例。介紹如何在高並發狀況下確保資料正確。
在高並發請求下容易參數兩個問題
1.資料出錯,導致產品超賣。
2.頻繁操作資料庫,導致效能下降。
測試環境
Windows7
apache2.4.9
php5.5.12
php框架yii2.0
工具apache bench(apache自帶高並發請求工具)。
通常處理方法
從控制器可以看出程式碼思路。先查詢商品庫存。若庫存大於0 ,則庫存減少1,同時生產訂單,輸入搶購者資料。
// 常规代码处理高并发 public function actionNormal(){ // 查询库存 $stock = Goods::find()->select('stock')->where(['goods_id'=>100001])->asArray()->one(); // 判断该商品是否还有库存 if ($stock['stock']>0) { // 库存减一 Goods::updateAllCounters(['stock' => -1],['goods_id'=>100001]); // 生产订单(另外功能,暂且随机赋值) $order = $this->build_order(); // 秒杀信息入库 $model = new Highly(); $model->order_id = $order; $model->goods_name = '秒杀商品'; $model->buy_time = date('Y-m-d H:i:s',time()); $model->mircrotime = microtime(true); if($model->save()===false){ echo '未能成功抢购!'; }else{ echo '恭喜你,订单<b>'.$order.'</b>抢购成功'; } }else{ echo '已被抢购一空!'; } }
將商品庫存設定為20後,透過ab 配置200的並發請求。
ab -n 200 -c 200 http//localhost/highly/normal
執行結果發現庫存變成了負值,商品超賣了。
原因比較簡單,在高並發請求下。在生產訂單,減少庫存之前,會優先查詢到庫存結果。
優化一:修改庫存資料型別
第一種最佳化方法,從資料庫著手。既然查詢到的結果不準確,那我就在庫存減少上做手腳。將庫存的資料型態改成無符號(不能有負值)。
程式碼還是跟上面差不多,只是在庫存減1的地方做了個判斷。避免報錯。
public function actionNormal(){ // 查询库存 $stock = Goods::find()->select('stock')->where(['goods_id'=>100001])->asArray()->one(); // 判断该商品是否还有库存 if ($stock['stock']>0) { // 库存减一 if(Goods::updateAllCounters(['stock' => -1],['goods_id'=>100001])===false){ echo "已被抢购一空!"; return false; } // 生产订单(另外功能,暂且随机赋值) $order = $this->build_order(); // 秒杀信息入库 $model = new Highly(); $model->order_id = $order; $model->goods_name = '秒杀商品'; $model->buy_time = date('Y-m-d H:i:s',time()); $model->mircrotime = microtime(true); if($model->save()===false){ echo '未能成功抢购!'; }else{ echo '恭喜你,订单<b>'.$order.'</b>抢购成功'; } }else{ echo '已被抢购一空!'; } }
這次同樣200的並發,執行結果發現。數據正確,並不會有超賣的情況。
思路其實也比較簡單。因為庫存不能為負值,當庫存等於0時,如果還有值傳進來,則會報錯。請求被終止。
這種最佳化方式,雖然避免了商品超賣的情況。但是另一方面,請求仍然會對資料庫造成壓力。如果多個功能使用此資料庫,會造成效能下降嚴重。
優化二:redis
利用 redis list類型的pop的原子性。在操作資料庫前,做一個驗證。當商品賣完後,就不允許再繼續進行資料庫作業。
// redis list 高并发测试 public function actionRedis(){ $redis = \Yii::$app->redis; // $redis->lpush('mytest',1); $order = $this->build_order(); // echo $order;die; // echo $redis->llen('mytest'); $reg = $redis->lpop('mytest'); if (!$reg) { echo "笨蛋!已经被抢光啦!"; return false; } $redis->close(); $model = new Highly(); $model->order_id = $order; $model->goods_name = '秒杀商品'; $model->buy_time = date('Y-m-d H:i:s',time()); $model->mircrotime = microtime(true); if($model->save()===false){ echo '未能成功抢购!'; }else{ echo '恭喜你,订单<b>'.$order.'</b>抢购成功'; } } // 给redis添加商品 public function actionInsertgoods(){ $count = yii::$app->request->get('count',0); if (empty($count)) { echo '大兄弟,你还没告诉我需要上架多少商品呢!'; return false; } $redis = \Yii::$app->redis; for ($i=0; $i < $count; $i++) { $redis->lpush('mytest',1); } echo '成功添加了'.$redis->llen('mytest').'件商品。'; $redis->close(); }
這一點的程式碼,我寫了兩個方法。第一個方法是秒殺的程式碼,第二個方法是為秒殺的商品設定數量。為了方便測試,我這裡處理的比較簡單。
通過測試,資料庫生產的訂單數量正常,並沒有出現問題。而又避免了請求資料庫造成效能下降的問題。同時記憶體資料庫redis查詢的速度比mysql快很多。
相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!
推薦閱讀:
以上是php處理搶購類別高並發請求實作詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!