这次给大家带来php+redis消息队列实现抢购步骤详解(附代码),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+redis消息队列实现抢购步骤详解(附代码)的详细内容。更多信息请关注PHP中文网其他相关文章!

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\ \;||\xc2\xa0)/","其他字符",$str)”语句。

查找方法:1、用strpos(),语法“strpos("字符串值","查找子串")+1”;2、用stripos(),语法“strpos("字符串值","查找子串")+1”。因为字符串是从0开始计数的,因此两个函数获取的位置需要进行加1处理。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具

WebStorm Mac版
好用的JavaScript开发工具

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。