Mysql版

逻辑步骤

  1. mysql存储引擎使用Innodb
  2. 开始事务,查询商品库存并加上共享锁
  3. 判断库存是否足够,进行商品/订单/用户等操作
  4. 提交事务,完成下单抢购

代码参考

    // 关闭自动提交
$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 (不限购)

逻辑步骤

  1. 以商品id生成key,redis获取库存,开启redis监控key和redis事务

    • 首次获取失败: 数据查询商品库存存入redis,$redis->set($key,$stock,['nx','ex'=>60])',(nx参数: 不存在时才设置,ex: 时效)
    • 非首次获取成功: 判断库存是否足够
  2. 开启数据库事务,减去库存,创建订单
  3. $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 (限购)

逻辑步骤

  1. 抢购前将商品库存放到队列list中[库存队列] (库存=队列长度)
  2. 抢购开始,将用户id放到hash队列中[排队hash队列],已存在:跳过.不存在:继续
  3. 减库存操作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'请勿重复请求';
}

原文地址: https://www.zhuyilong.我爱你/tech/php_concurrent_buying.html

最新文章

  1. Spring任务调度之Spring-Task
  2. Android打开相机和打开相册
  3. Z/OS遇到的错误
  4. [NOIP2016-day1-T2]天天爱跑步running_题解
  5. struts2 + ajax + json的结合使用,实例讲解
  6. linux终端vi同时显示多个文件的分屏操作及切换操作
  7. Yii2文件上传
  8. poi 读取 excel(.xlsx) 2007及以上版本
  9. HDU 4593 H - Robot 水题
  10. 使用DOM4J解析XMl文件与读取XML文件
  11. winform自动更新并实现文件的批量异步下载
  12. Sign http
  13. c# String ,String[] 和 List&lt;String&gt;之间的转换
  14. visual studio 中sstrcpy报错的问题
  15. centOS7.3新安装后,设置IP,以及Putty远程和Xshell远程 (学习是个持续的过程,也许中途放松过,但是仍然能重新捡起来,并学以致用,方为真勇士)
  16. Vue+Django2.0 restframework打造前后端分离的生鲜电商项目(3)
  17. 搭建前端监控系统(四)Js截图上报篇
  18. [Spring] 学习Spring Boot之二:整合MyBatis并使用@Trasactional管理事务
  19. c#设计应用程序单实例运行
  20. android 关于view的onTouch和onClick同时触发解决方案

热门文章

  1. GitHub 500 error
  2. Angular Learning Paths
  3. anatomy app
  4. let &amp; var &amp; initialized bug
  5. 为什么Linux默认页大小是4KB
  6. Elasticsearch---DSL搜索实践
  7. A study on ILC for linear discrete systems with single delay
  8. VUE实现富文本编辑以及组件传值的使用总结
  9. 微信小程序:利用map方法方便获得对象数组中的特定属性值们
  10. Linux fork()一个进程内核态的变化