- 使用 Redis 得 LIST, 操作 LPOP
- 使用乐观锁 悲观锁。
- 目的保持数据操作的 原子性。
mysql 表
fa_flash_product 产品表 id num 库存 version 版本号
fa_flash 成功订单表 id userid 用户id msg 描述
redis 队列
product 产品库存 队列
/**
* 初始化 初始化产品库存
*/
public function initproduct()
{
// dump(Redis::sMembers('product'));
dump(Redis::sMembers('user_product'));
Redis::del('product');
Redis::del('user_product');
$size = 5;
for ($i = 0; $i < $size; $i++) {
Redis::lPush('product', 1);
}
if (Redis::lLen('product') == 5) {
echo '初始化 成功';
} else {
echo '初始化 失败';
}
}
/**
* 抢购
*
* 场景:并发环境 秒杀
* 10 产品
*
*/
public function buyproduct()
{
$useid = rand(1000, 2000);
/**
* 正常 mysql 没有优化
* 库存 负数,订单超卖
*/
$num = db::table('fa_flash_product')->where('id',1)->value('num');
if ($num > 0) {
//库存足够
//库存减少 订单增加
db::table('fa_flash_product')->where('id',1)->setDec('num');
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '抢购成功', 'time' => date('Y-m-d H:i:s')]);
}
/**
* redis 队列 测试
* 订单没有超卖
*/
//移除产品库存队列
if (Redis::lPop('product')) {
//成功
//移除 库存队列;
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '抢购成功', 'time' => date('Y-m-d H:i:s')]);
} else {
//失败
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '秒杀已结束', 'time' => date('Y-m-d H:i:s')]);
}
/**
* 正常 mysql 乐观锁 版本号 version
* 库存 正常,订单正常
*/
$find = db::table('fa_flash_product')->where('id', 1)->find();
if ($find['num'] > 0) {
//库存足够
//库存减少 订单增加
$is_success = db::table('fa_flash_product')->where(['id' => 1, 'version' => $find['version']])->update(['version' => $find['version'] + 1, 'num' => $find['num'] - 1]);
if ($is_success) {
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '抢购成功', 'time' => date('Y-m-d H:i:s')]);
}
}
/**
* redis 乐观锁
* watch 监控 开启事务
*
*/
Redis::watch('stock');//乐观锁 监视作用 set() 初始值 0
//开启事务
$stock = Redis::get('stock'); //库存
if ($stock) {
//库存 大约零
Redis::multi();
Redis::set('stock', $stock - 1);//扣库存 //返回扣库存后的值
$res = Redis::exec();//返回事务内,所有命令的返回值,按命令先后排序,当操作别打断时,返回空
if ($res) {
//秒杀成功
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '抢购成功', 'time' => date('Y-m-d H:i:s')]);
} else {
//秒杀失败
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '秒杀失败,请重试', 'time' => date('Y-m-d H:i:s')]);
}
} else {
//秒杀结束了
db::table('fa_flash')->insert(['userid' => $useid, 'msg' => '秒杀结束了', 'time' => date('Y-m-d H:i:s')]);
}
}
ab 测试工具
ab.exe -n50 -c50
上述暂时简单的处理 库存超卖现象。后续 继续完善 代码
框架 tp5 Redis 操作类 https://github.com/xiucaiwu/tp5redis
简单了解 redis https://m.runoob.com/redis/redis-data-types.html