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

PHP并发抢购解决方案的更多相关文章

  1. 关于PHP高并发抢购系统设计

    内容并发抢购系统注意事项高并发架构设计描述程序端核心代码实现订单流程mysql 端并发解决方案注意事项(1)高并发环境下,对于服务器cup.内存.网络宽带使用率会瞬间暴涨,需要注意对同服务器上其他应用 ...

  2. Atitit.并发测试解决方案(2) -----获取随机数据库记录 随机抽取数据 随机排序 原理and实现

    Atitit.并发测试解决方案(2) -----获取随机数据库记录 随机抽取数据 随机排序 1. 应用场景 1 2. 随机抽取数据原理 1 3. 常用的实现方法:::数据库随机函数 1 4. Mssq ...

  3. PHP 高并发秒杀解决方案

    本文提供 PHP 高并发秒杀解决方案(附加三个案例说明(普通流程,使用文件锁,使用redis消息队列)) 1:(正常流程,不做任何高并发处理),代码如下: <?php $_mysqli = ne ...

  4. C# 数据库并发的解决方案(通用版、EF版)

    自ASP.NET诞生以来,微软提供了不少控制并发的方法,在了解这些控制并发的方法前,我们先来简单介绍下并发! 并发:同一时间或者同一时刻多个访问者同时访问某一更新操作时,会产生并发! 针对并发的处理, ...

  5. php高并发秒杀解决方案

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/super_runman/article/details/53037151 在秒杀.抢火车票等地方,我 ...

  6. quartz集群分布式(并发)部署解决方案-Spring

    项目中使用分布式并发部署定时任务,多台跨JVM,按照常理逻辑每个JVM的定时任务会各自运行,这样就会存在问题,多台分布式JVM机器的应用服务同时干活,一个是加重服务负担,另外一个是存在严重的逻辑问题, ...

  7. 高并发简单解决方案————redis队列缓存+mysql 批量入库(ThinkPhP)

    问题分析 问题一:要求日志最好入库:但是,直接入库mysql确实扛不住,批量入库没有问题,done.[批量入库和直接入库性能差异] 问题二:批量入库就需要有高并发的消息队列,决定采用redis lis ...

  8. 转载:【高并发简单解决方案 | 靠谱崔小拽 】redis队列缓存 + mysql 批量入库 + php离线整合

    需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

  9. 基于JVM规范的并发编程解决方案

    在并发的世界里,选择合适的状态处理方法将对并发性和正确性起到决定性的影响.这方面可选的方法有:共享可变性.隔离可变性以及完全不可变性. 对于并发问题来说最好的解决方法是从根本上消灭它而不是花很多时间解 ...

随机推荐

  1. Document This All In One

    Document This All In One Document This 自定义注释 @author vscode 自定义注释 "docthis.automaticForBlockCom ...

  2. 使用 js 实现一个简易版的 GIPHY 动图搜索 web 应用程序

    使用 js 实现一个简易版的 GIPHY 动图搜索 web 应用程序 具有挑战性的前端面试题 API JAMstack refs https://www.infoq.cn/article/0NUjpx ...

  3. 如何在 macOS 上进行滚动截屏

    如何在 macOS 上进行滚动截屏 Shift-Command-5 https://support.apple.com/zh-cn/guide/mac-help/mh26782/mac demo Xn ...

  4. Google IO & 2019

    Google IO & 2019 Google IO Recap \ https://www.techradar.com/news/google-io-2019-keynote https:/ ...

  5. 「NGK每日快讯」2021.2.2日NGK公链第91期官方快讯!

  6. 下一代币王花落谁家?是否是BGV更胜一筹呢?

    BGV作为Baccarat的平台币横空出世,通过BGV来激励拥有NGK的用户.在共识算法上,Baccarat 在共识层采用了混合机制 DPOSS共识机制,这是维护 Baccarat 生态体系良性发展的 ...

  7. go-admin在线开发平台学习-1[安装、配置、启动]

    项目介绍 go-admin 是一个中后台管理系统,基于(gin, gorm, Casbin, Vue, Element UI)实现.主要目的是为了让开发者更专注业务,减少重复代码的编写,节省时间,提升 ...

  8. flex图片垂直居中

    html <view class="person_info_more"> <image class="more" src="/ima ...

  9. python行与列显示不全

    在显示数据框时添加以下代码 #显示所有列 pd.set_option('display.max_columns', None) #显示所有行 pd.set_option('display.max_ro ...

  10. mysql锁——innodb的行级锁

    [前言]数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则.MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应 ...