本文以搶購、秒殺為例。介紹如何在高并發狀況下確保數據正確。
在高并發請求下容易參數兩個問題
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解決高并發的優化方案實例
- php多進程模擬并發事務產生的問題小結
- PHP利用Mysql鎖解決高并發的方法
- php curl批處理實現可控并發異步操作示例
- PHP+Redis 消息隊列 實現高并發下注冊人數統計的實例
- PHP開發中解決并發問題的幾種實現方法分析
- PHP使用Redis實現防止大并發下二次寫入的方法
- php結合redis高并發下發帖、發微博的實現方法
- 詳解php處理大并發大流量大存儲