php处理抢购类功能的高并发请求


Posted in PHP onFebruary 08, 2018

本文以抢购、秒杀为例。介绍如何在高并发状况下确保数据正确。
在高并发请求下容易参数两个问题
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

执行结果发现库存变成了负值,商品超卖了。

php处理抢购类功能的高并发请求

原因比较简单,在高并发请求下。在生产订单,减少库存之前,会优先查询到库存结果。

优化一:修改库存数据类型

第一种优化方法,从数据库入手。既然查询到的结果不准确,那我就在库存减少上做手脚。将库存的数据类型改成无符号(不能有负值)。

代码还是跟上面差不多,只是在库存减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 相关文章推荐
MYSQL环境变量设置方法
Jan 15 PHP
Linux下ZendOptimizer的安装与配置方法
Apr 12 PHP
令PHP初学者头疼十四条问题大总结
Nov 12 PHP
php中强制下载文件的代码(解决了IE下中文文件名乱码问题)
May 09 PHP
浅析51个PHP处理字符串的函数
Aug 02 PHP
Drupal7连接多个数据库及常见问题解决
Mar 02 PHP
zf框架的数据库追踪器使用示例
Mar 13 PHP
PHP加密3DES报错 Call to undefined function: mcrypt_module_open() 如何解决
Apr 17 PHP
laravel学习教程之关联模型
Jul 30 PHP
php取出数组单个值的方法
Mar 12 PHP
Laravel路由研究之domain解决多域名问题的方法示例
Apr 04 PHP
php 策略模式原理与应用深入理解
Sep 25 PHP
php+redis实现商城秒杀功能
Nov 19 #PHP
php+redis消息队列实现抢购功能
Feb 08 #PHP
PHP多线程模拟实现秒杀抢单
Feb 07 #PHP
PHP设计模式之装饰器模式实例详解
Feb 07 #PHP
PHP使用星号替代用户名手机和邮箱的实现代码
Feb 07 #PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
Feb 07 #PHP
php删除一个路径下的所有文件夹和文件的方法
Feb 07 #PHP
You might like
php下批量挂马和批量清马代码
2011/02/27 PHP
jQuery EasyUI API 中文文档 - DateBox日期框
2011/10/15 PHP
php生成毫秒时间戳的实例讲解
2017/09/22 PHP
Windows上php5.6操作mongodb数据库示例【配置、连接、获取实例】
2019/02/13 PHP
firefox浏览器下javascript 拖动层效果与原理分析代码
2007/12/04 Javascript
Javascript表达式中连续的 &amp;&amp; 和 || 之赋值区别
2010/10/17 Javascript
模拟电子签章盖章效果的jQuery插件源码
2013/06/24 Javascript
了解了这些才能开始发挥jQuery的威力
2013/10/10 Javascript
jQuery 追加元素的方法如append、prepend、before
2014/01/16 Javascript
jQuery验证元素是否为空的两种常用方法
2015/03/17 Javascript
jQuery实现网页抖动的菜单抖动效果
2015/08/07 Javascript
基于jQuery实现仿搜狐辩论投票动画代码(附源码下载)
2016/02/18 Javascript
使用getBoundingClientRect方法实现简洁的sticky组件的方法
2016/03/22 Javascript
AngularJS基础 ng-switch 指令简单示例
2016/08/03 Javascript
通过JS和PHP两种方法判断用户请求时使用的浏览器类型
2016/09/01 Javascript
详解webpack3编译兼容IE8的正确姿势
2017/12/21 Javascript
红黑树的插入详解及Javascript实现方法示例
2018/03/26 Javascript
说说Vue.js中的functional函数化组件的使用
2019/02/12 Javascript
Vue搭建后台系统需要注意的问题
2019/11/08 Javascript
[05:08]顺网杯ISS-DOTA2赛歌 少女偶像Lunar青春演绎
2013/12/05 DOTA
python重试装饰器示例
2014/02/11 Python
Python使用gensim计算文档相似性
2016/04/10 Python
Flask框架中密码的加盐哈希加密和验证功能的用法详解
2016/06/07 Python
对Python3使运行暂停的方法详解
2019/02/18 Python
python 判断三个数字中的最大值实例代码
2019/07/24 Python
python实现超市商品销售管理系统
2019/11/22 Python
Python中sys模块功能与用法实例详解
2020/02/26 Python
Python环境配置实现pip加速过程解析
2020/11/27 Python
浅谈CSS3中的变形功能-transform功能
2017/12/27 HTML / CSS
既然说Ruby中一切都是对象,那么Ruby中类也是对象吗
2013/01/26 面试题
护理专业自荐书
2014/06/04 职场文书
酒店工程部岗位职责
2015/02/12 职场文书
《中国古代诗歌散文欣赏》高中语文教材
2019/08/20 职场文书
python 破解加密zip文件的密码
2021/04/22 Python
7个你应该知道的JS原生错误类型
2021/04/29 Javascript
matlab xlabel位置的设置方式
2021/05/21 Python