php+redis消息队列实现抢购功能


Posted in PHP onFebruary 08, 2018

本文实例为大家分享了php+redis消息队列实现抢购的具体代码,供大家参考,具体内容如下

实现功能:

1. 基于redis队列,防止高并发的超卖
2. 基于mysql的事务加排它锁,防止高并发的超卖

基于redis队列工作流程:

1. 管理员根据goods表中的库存,创建redis商品库存队列
2. 客户端访问秒杀API
3. web服务器先从redis的商品库存队列中查询剩余库存重点内容
4. redis队列中有剩余,则在mysql中创建订单,去库存,抢购成功
5. redis队列中没有剩余,则提示库存不足,抢购失败重点内容

基于mysql事务和排它锁工作流程:

1. 开启事务
2. 查询库存,并显示的设置写锁(排他锁):SELECT * FROM goods WHERE id = 1 FOR UPDATE
3. 生成订单
4. 去库存,隐示的设置写锁(排他锁):UPDATE goods SET counts = counts ? 1 WHERE id = 1
5. commit,释放锁

注意:第二步步可以设置共享锁,不然有可能会造成死锁。

代码:

<?php
/**********************************************
* 抢购模块
*
* @author liubin
* @date 2016-02-10
*
* ab -n 1000 -c 100 http://192.168.16.73/Seckill/buy.php
*
*/
class seckill extends common
{

 private $_orderModel = null;
 private $_goodsModel = null;
 private $_redis = null;
 /*
  * 错误信息
 */
 protected $_error = '';
 /**
  * 构造器
  *
 */
 public function __construct()
 {
  if($this->_orderModel === null){
   $this->_orderModel = new OrderModel();
  }
  if($this->_goodsModel === null){
   $this->_goodsModel = new GoodsModel();
  }
  if($this->_redis === null){
   $this->_redis = new QRedis(); 
  }
 }
 /*
  * 秒杀API
  * 
  * @author liubin
  * @date 2017-02-10
 */
 public function addQsec(){
  $gid = intval($_GET['gid']);
  $type = isset($_GET['type']) ? $_GET['type'] : 'mysql';
  switch ($type) {
   case 'mysql':
    $this->order_check_mysql($gid);
    echo $this->getError();
    break;
   case 'redis':
    $this->order_check_redis($gid);
    echo $this->getError();
    break;
   case 'transaction':
    $this->order_check_transaction($gid);
    echo $this->getError();
    break;
   default:
    echo '类型错误';
    break;
  }
 }
 /*
  * 获取错误信息
  * 
  * @author liubin
  * @date 2017-02-10
 */
 public function getError(){
  return $this->_error;
 }
 /*
  * 基于mysql验证库存信息
  * @desc 高并发下会导致超卖
  *
  * @author liubin
  * @date 2017-02-10
 */
 protected function order_check_mysql($gid){


  $model = $this->_goodsModel;
  $pdo = $model->getHandler();
  $gid = intval($gid);

  /*
   * 1:$sql_forlock如果不加事务,不加写锁:
   * 超卖非常严重,就不说了
   * 
   * 2:$sql_forlock如果不加事务,只加写锁:
   * 第一个会话读$sql_forlock时加写锁,第一个会话$sql_forlock查询结束会释放该行锁.
   * 第二个会话在第一个会话释放后读$sql_forlock的写锁时,会再次$sql_forlock查库存
   * 导致超卖现象产生
   *
  */
  $sql_forlock = 'select * from goods where id = '.$gid .' limit 1 for update';
  //$sql_forlock = 'select * from goods where id = '.$gid .' limit 1';
  $result = $pdo->query($sql_forlock,PDO::FETCH_ASSOC);
  $goodsInfo = $result->fetch();

  if($goodsInfo['counts']>0){

   //去库存
   $gid = $goodsInfo['id'];
   $sql_inventory = 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
   $result = $this->_goodsModel->exect($sql_inventory);
   if($result){
    //创订单
    $data    = [];
    $data['order_id'] = $this->_orderModel->buildOrderNo();
    $data['goods_id'] = $goodsInfo['id'];
    $data['addtime'] = time();
    $data['uid']  = 1;
    $order_rs = $this->_orderModel->create_order($data);
    if($order_rs){
     $this->_error = '购买成功';
     return true;
    }
   }
  }

  $this->_error = '库存不足';
  return false;

 }
 /*
  * 基于redis队列验证库存信息
  * @desc Redis是底层是单线程的,命令执行是原子操作,包括lpush,lpop等.高并发下不会导致超卖
  *
  * @author liubin
  * @date 2017-02-10
 */
 protected function order_check_redis($gid){
  $goodsInfo = $this->_goodsModel->getGoods($gid);
  if(!$goodsInfo){
   $this->_error = '商品不存在';
   return false;
  }
  $key = 'goods_list_'.$goodsInfo['id'];
  $count = $this->_redis->getHandel()->lpop($key);
  if(!$count){
   $this->_error = '库存不足';
   return false;
  }
  //生成订单
  $data    = [];
  $data['order_id'] = $this->_orderModel->buildOrderNo();
  $data['goods_id'] = $goodsInfo['id'];
  $data['addtime'] = time();
  $data['uid']  = 1;
  $order_rs = $this->_orderModel->create_order($data);

  //库存减少
  $gid = $goodsInfo['id'];
  $sql = 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
  $result = $this->_goodsModel->exect($sql);
  $this->_error = '购买成功';
  return true;
 }
 /*
  * 基于mysql事务验证库存信息
  * @desc 事务 和 行锁 模式,高并发下不会导致超卖,但效率会慢点
  * @author liubin
  * @date 2017-02-10


  说明:
  如果$sql_forlock不加写锁,并发时,$sql_forlock查询的记录存都大于0,可以减库存操作.
  如果$sql_forlock加了写锁,并发时,$sql_forlock查询是等待第一次链接释放后查询.所以库存最多就是5

 */
 protected function order_check_transaction($gid){

  $model = $this->_goodsModel;
  $pdo = $model->getHandler();
  $gid = intval($gid);

  try{
   $pdo->beginTransaction();//开启事务处理


   /*
    * 1:$sql_forlock如果只加事务,不加写锁:
    * 开启事务
    * 因为没有加锁,读$sql_forlock后,并发时$sql_inventory之前还可以再读。
    * $sql_inventory之后和commit之前才会锁定
    * 出现超卖跟事务的一致性不冲突
    * 
    *
    * 2:$sql_forlock如果加了事务,又加读锁:
    * 开启事务
    * 第一个会话读$sql_forlock时加读锁,并发时,第二个会话也允许获得$sql_forlock的读锁,
    * 但是在第一个会话执行去库存操作时(写锁),写锁便会等待第二个会话的读锁,第二个会话执行写操作时,写锁便会等待第一个会话的读锁,
    * 出现死锁

    * 3:$sql_forlock如果加了事务,又加写锁:
    * 开启事务
    * 第一个会话读$sql_forlock时加写锁,直到commit才会释放写锁,并发查询不会出现超卖现象。
    *
   */

   $sql_forlock = 'select * from goods where id = '.$gid .' limit 1 for update';
   //$sql_forlock = 'select * from goods where id = '.$gid .' limit 1 LOCK IN SHARE MODE';
   //$sql_forlock = 'select * from goods where id = '.$gid .' limit 1';
   $result = $pdo->query($sql_forlock,PDO::FETCH_ASSOC);
   $goodsInfo = $result->fetch();

   if($goodsInfo['counts']>0){

    //去库存
    $gid = $goodsInfo['id'];
    $sql_inventory = 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
    $result = $this->_goodsModel->exect($sql_inventory);

    if(!$result){
     $pdo->rollBack();
     $this->_error = '库存减少失败';
     return false;
    }

    //创订单
    $data    = [];
    $data['id']   = 'null';
    $data['order_id'] = $this->_orderModel->buildOrderNo();
    $data['goods_id'] = $goodsInfo['id'];
    $data['uid']  = 'abc';
    $data['addtime'] = time();

    $sql = 'insert into orders (id,order_id,goods_id,uid,addtime) values ('.$data['id'].',"'.$data['order_id'].'","'.$data['goods_id'].'","'.$data['uid'].'","'.$data['addtime'].'")';   
    $result = $pdo->exec($sql);
    if(!$result){
     $pdo->rollBack();
     $this->_error = '订单创建失败';
     return false;
    }
    $pdo->commit();//提交
    $this->_error = '购买成功';
    return true;

   }else{
    $this->_error = '库存不足';
    return false;
   }
  }catch(PDOException $e){
   echo $e->getMessage();
   $pdo->rollBack();
  }


 }
 /*
  * 创建订单
  * mysql 事物处理,也可以用存储过程
  *
 */
 private function create_order($goodsInfo){
  //生成订单
  $data    = [];
  $data['order_id'] = $this->_orderModel->buildOrderNo();
  $data['goods_id'] = $goodsInfo['id'];
  $data['addtime'] = time();
  $data['uid']  = 1;
  $order_rs = $this->_orderModel->create_order($data);

  //库存减少
  $gid = $goodsInfo['id'];
  $sql = 'UPDATE goods SET counts = counts - 1 WHERE id = '.$gid;
  $result = $this->_goodsModel->exect($sql);
  return true;
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
一个PHP日历程序
Dec 06 PHP
php日历[测试通过]
Mar 27 PHP
Smarty安装配置方法
Apr 10 PHP
PHP 替换模板变量实现步骤
Aug 24 PHP
php Notice: Undefined index 错误提示解决方法
Aug 29 PHP
PHP函数之error_reporting(E_ALL ^ E_NOTICE)详细说明
Jul 01 PHP
PHP获取MAC地址的具体实例
Dec 13 PHP
php中的观察者模式简单实例
Jan 20 PHP
PHP从FLV文件获取视频预览图的方法
Mar 12 PHP
php自动识别文字编码并转换为目标编码的方法
Aug 08 PHP
浅谈PHP安全防护之Web攻击
Jan 03 PHP
laravel框架中控制器的创建和使用方法分析
Nov 23 PHP
PHP多线程模拟实现秒杀抢单
Feb 07 #PHP
PHP设计模式之装饰器模式实例详解
Feb 07 #PHP
PHP使用星号替代用户名手机和邮箱的实现代码
Feb 07 #PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
Feb 07 #PHP
php删除一个路径下的所有文件夹和文件的方法
Feb 07 #PHP
浅析PHP类的反射来实现依赖注入过程
Feb 06 #PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
Feb 06 #PHP
You might like
php+ajax实现无刷新分页
2015/11/18 PHP
修改WordPress中文章编辑器的样式的方法详解
2015/12/15 PHP
thinkphp跨库操作的简单代码实例
2016/09/22 PHP
PHP实现的简单操作SQLite数据库类与用法示例
2017/06/19 PHP
JQuery 拾色器插件发布-jquery.icolor.js
2010/10/20 Javascript
JavaScript中的值类型详细介绍
2014/12/29 Javascript
浅析2种JavaScript继承方式
2015/12/04 Javascript
jquery实现可旋转可拖拽的文字效果代码
2016/01/27 Javascript
AngularJS ng-bind-template 指令详解
2016/07/30 Javascript
JS中使用DOM来控制HTML元素
2016/07/31 Javascript
详解Vue2.0之去掉组件click事件的native修饰
2017/04/20 Javascript
AngularJS封装$http.post()实例详解
2017/05/06 Javascript
详解extract-text-webpack-plugin 的使用及安装
2018/06/12 Javascript
通过jquery.cookie.js实现记住用户名、密码登录功能
2018/06/20 jQuery
Vue 组件参数校验与非props特性的方法
2019/02/12 Javascript
vue和H5 draggable实现拖拽并替换效果
2020/07/29 Javascript
vue 判断页面是首次进入还是再次刷新的实例
2020/11/05 Javascript
JavaScript函数柯里化实现原理及过程
2020/12/02 Javascript
[44:09]DOTA2上海特级锦标赛A组小组赛#1 EHOME VS MVP.Phx第二局
2016/02/25 DOTA
[39:19]完美世界DOTA2联赛PWL S2 SZ vs LBZS 第二场 11.26
2020/11/30 DOTA
Python基于sftp及rsa密匙实现远程拷贝文件的方法
2016/09/21 Python
Python iter()函数用法实例分析
2018/03/17 Python
PyCharm代码格式调整方法
2018/05/23 Python
Python字典推导式将cookie字符串转化为字典解析
2019/08/10 Python
HTML5的文档结构和新增标签完全解析
2017/04/21 HTML / CSS
Vans荷兰官方网站:美国南加州的原创极限运动潮牌
2018/01/23 全球购物
Europcar德国:全球汽车租赁领域的领导者
2018/08/15 全球购物
豪华复古化妆:Besame Cosmetics
2019/09/06 全球购物
Sisley法国希思黎美国官方网站:享誉全球的奢华植物美容品牌
2020/06/27 全球购物
亲子读书活动方案
2014/02/22 职场文书
积极向上的团队口号
2014/06/06 职场文书
授权委托书怎么写
2014/09/25 职场文书
2015年全国爱耳日活动总结
2015/02/27 职场文书
2016年大学生寒假社会实践心得体会
2015/10/09 职场文书
ElementUI实现el-form表单重置功能按钮
2021/07/21 Javascript
nginx负载功能+nfs服务器功能解析
2022/02/28 Servers