php抢购问题,PHP并发抢购解决方案

Mysql版

逻辑步骤

mysql存储引擎使用Innodb

开始事务,查询商品库存并加上共享锁

判断库存是否足够,进行商品/订单/用户等操作

提交事务,完成下单抢购

代码参考

// 关闭自动提交

$this->db_conn->autocommit(FALSE);//开启事务

// //获取商品库存

$query_sql = 'select stock from goods where id ='.$goods_id .' lock in share mode'; //加mysql共享锁[提交前不允许其他事务修改]

$stock = $this->query($query_sql);

if ($stock < $num) {

return $this->log('库存不足_'.$stock);

}

//减库存

$sql1 = 'update goods set stock = stock-'.$num.' where id='.$goods_id;

if (!$this->db_conn->query($sql1)) {

$this->db_conn->rollback();

return $this->log('商品库存更新失败sql: '.$sql1.'==>stock: '.$stock);

}

//创建订单

$order_sn = date('YmdHis') . rand(1000,9999);

$sql2 = 'insert into `order` (order_sn,goods_id,goods_num,user_id) values ("'.$order_sn.'",'.$goods_id.','.$num.','.$user_id.')';

if (!$this->db_conn->query($sql2)) {

$this->db_conn->rollback();

return $this->log('创建订单失败: '.$sql2.'==>stock: '.$stock);

}

//提交事务

$this->db_conn->commit();

return true;

Redis版

redis事务/watch/setnx (不限购)

逻辑步骤

以商品id生成key,redis获取库存,开启redis监控key和redis事务首次获取失败: 数据查询商品库存存入redis,$redis->set($key,$stock,['nx','ex'=>60])'

,(nx

参数: 不存在时才设置,ex

: 时效)

非首次获取成功: 判断库存是否足够

开启数据库事务,减去库存,创建订单

$redis->exec();

redis执行,失败数据库rollback

;成功数据库commit

;

代码参考

//连接redis

$redis = new Redis();

$redis->connect('127.0.0.1', 6379);

$redis->auth('zylwan@Redis123'); //密码验证

if (!$redis) {

return $this->log('redis连接失败');

}

$redis->select(9);//选择数据库9

$stock_key = 'goods_id_stock_'.$goods_id; //商品库存key

$stock = $redis->get($stock_key);

//监控key

$redis->watch($stock_key); //监控下单过程中key是否被修改

//开启事务

$redis->multi();

if ($stock === false) { //首次设置key

$query_sql = 'select stock from goods where id='.$goods_id;

stock = $this->query($query_sql);

$redis->set($stock_key,$stock,['nx','ex'=>60]); //nx参数: 当key不存在时才设置

}

if ($stock < $num) {

return $this->log('商品库存不足num: '.$num.'==>stock: '.$stock);

}

$redis->decr($stock_key);//redis减库存

// 关闭mysql自动提交

$this->db_conn->autocommit(FALSE);//开启sql事务

$sql1 = 'update goods set stock = stock-'.$num.' where id='.$goods_id; //减mysql库存

if (!$this->db_conn->query($sql1)) {

$this->db_conn->rollback();

return $this->log('商品库存更新失败sql: '.$sql1.'==>stock: '.$stock);

}

//开单

$order_sn = date('YmdHis') . rand(1000,9999);

$sql2 = 'insert into `order` (order_sn,goods_id,goods_num,user_id) values ("'.$order_sn.'",'.$goods_id.','.$num.','.$user_id.')';

if (!$this->db_conn->query($sql2)) {

$this->db_conn->rollback();

return $this->log('创建订单失败: '.$sql2.'==>stock: '.$stock);

}

$res = $redis->exec(); //执行redis事务

if ($res === false) { //watch监控到key被修改,redis操作没有执行==>mysql操作回滚

$this->db_conn->rollback();

return $this->log('redis操作失败: '.'==>stock: '.$stock);

}

// 都成功执行,提交事务

$this->db_conn->commit();

return true;

redis list/hash (限购)

逻辑步骤

抢购前将商品库存放到队列list中[库存队列

] (库存=队列长度)

抢购开始,将用户id放到hash队列中[排队hash队列

],已存在:跳过.不存在:继续

减库存操作lpop(list)

:成功=>数据库下单等,存储用户id到抢购成功list队列中[抢购成功hash队列

]

失败=>无库存,抢光了

代码参考

$user_id = 1;

$wait_key = "user_wait";//用户抢购请求hash队列

$user_key = "user";//用户抢购成功list队列

$stock_key = "goods_stock";//商品库存队列[在抢购开始前生成]

$result =$redis->hset($wait_key, $user_id, $user_id); //抢购用户排队,user_id去重

if ($result) { //排队成功 => 开始抢购

$count = $redis->lpop($stock_key); //扣减库存

/**

* list列表的原子性确保了此处并发时的串行

* 即: 确保了下面if判断中只有与库存数量相等的人数可以进入到else中抢购下单

*/

if (!$count) { //扣减失败 => 抢光了

z_log('已经抢光了哦_'.$user_id);

} else { //扣减成功

$redis->lpush($user_key, $user_id);

$result =$redis->hset($user_key, $user_id, $user_id);

//

//.....数据库下单操作...

//

echo'抢购成功';

}

} else { // 重复排队=>排队失败

echo'请勿重复请求';

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值