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获得文件扩展名三法
Nov 25 PHP
自动把纯文本转换成Web页面的php代码
Aug 27 PHP
php 获取客户端的真实ip
Nov 30 PHP
解决PHP在DOS命令行下却无法链接MySQL的技术笔记
Dec 29 PHP
php 生成文字png图片的代码
Apr 17 PHP
php提示无法加载或mcrypt没有找到 PHP 扩展 mbstring解决办法
Mar 27 PHP
header中Content-Disposition的作用与使用方法
Jun 13 PHP
php页面缓存方法小结
Jan 10 PHP
php修改数组键名的方法示例
Apr 15 PHP
php使用curl实现简单模拟提交表单功能
May 15 PHP
CMSPRESS 10行代码搞定 PHP无限级分类2
Mar 30 PHP
PHP实现的超长文本分页显示功能示例
Jun 04 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
ThinkPHP中的关联模型注意点
2014/06/16 PHP
利用php实现一周之内自动登录存储机制(cookie、session、localStorage)
2016/10/31 PHP
php登录超时检测功能实例详解
2017/03/21 PHP
php连接MSsql server的五种方法总结
2018/03/04 PHP
百度移动版的url编码解码示例
2014/04/29 Javascript
jQuery 处理页面的事件详解
2015/01/20 Javascript
JavaScript动态加载样式表的方法
2015/03/21 Javascript
jQuery 判断图片是否加载完成方法汇总
2015/08/10 Javascript
jQuery实现的精美平滑二级下拉菜单效果代码
2016/03/28 Javascript
jquery插件bootstrapValidator数据验证详解
2016/11/09 Javascript
jQuery实现简易的输入框字数计数功能示例
2017/01/16 Javascript
Vue学习笔记进阶篇之多元素及多组件过渡
2017/07/19 Javascript
js中arguments对象的深入理解
2019/05/14 Javascript
重置Redux的状态数据的方法实现
2019/11/18 Javascript
javascript设计模式 ? 中介者模式原理与用法实例分析
2020/04/20 Javascript
在vue-cli创建的项目中使用sass操作
2020/08/10 Javascript
vue项目中播放rtmp视频文件流的方法
2020/09/17 Javascript
解决vue下载后台传过来的乱码流的问题
2020/12/05 Vue.js
[02:16]DOTA2英雄基础教程 干扰者
2014/01/15 DOTA
[04:15]DOTA2-DPC中国联赛 正赛 Ehome vs Aster 选手采访
2021/03/11 DOTA
Python 匹配任意字符(包括换行符)的正则表达式写法
2009/10/29 Python
python实现的登录和操作开心网脚本分享
2014/07/09 Python
Django中URLconf和include()的协同工作方法
2015/07/20 Python
pandas 选择某几列的方法
2018/07/03 Python
对pandas中iloc,loc取数据差别及按条件取值的方法详解
2018/11/06 Python
查看Python依赖包及其版本号信息的方法
2019/08/13 Python
HTML中使用SVG与SVG预定义形状元素介绍
2013/06/28 HTML / CSS
Emporio Armani腕表天猫官方旗舰店:乔治·阿玛尼为年轻人设计的副线品牌
2017/07/02 全球购物
最新的小工具和卓越的产品设计:Oh That Tech!
2019/08/07 全球购物
美国珠宝精品店:Opulent Jewelers
2019/08/20 全球购物
安全生产活动月方案
2014/03/09 职场文书
赔偿协议书范本
2014/04/15 职场文书
党校学习党性分析材料
2014/12/19 职场文书
2016同学毕业寄语大全
2015/12/04 职场文书
创业计划书之干洗店
2019/09/10 职场文书
Python保存并浏览用户的历史记录
2022/04/29 Python