抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:
1 高并发对数据库产生的压力
2 竞争状态下如何解决库存的正确减少("超卖"问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。
重点在于第二个问题

常规写法:

查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数

<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn){  echo "connect failed";  exit;
}
mysql_select_db("big",$conn);
mysql_query("set names utf8");$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;//生成唯一订单
function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){global $conn;$sql="insert into ih_log(event,type) values('$event','$type')";  mysql_query($sql,$conn);
}//模拟下单操作
//库存是否大于0
$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";//解锁 此时ih_store数据中goods_id='$goods_id' and sku_id='$sku_id' 的数据被锁住(注3),其它事务必须等待此次事务 提交后才能执行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row['number']>0){//高并发下会导致超卖$order_sn=build_order_no();//生成订单  $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')";  $order_rs=mysql_query($sql,$conn); //库存减少$sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);  if(mysql_affected_rows()){  insertLog('库存减少成功');}else{  insertLog('库存减少失败');}
}else{insertLog('库存不够');
}
?>

  优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

//库存减少
$sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows()){  insertLog('库存减少成功');
}

  优化方案2:使用mysql的事务,锁住操作的行

<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn){  echo "connect failed";  exit;
}
mysql_select_db("big",$conn);
mysql_query("set names utf8");$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;//生成唯一订单号
function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){global $conn;$sql="insert into ih_log(event,type) values('$event','$type')";  mysql_query($sql,$conn);
}//模拟下单操作
//库存是否大于0
mysql_query("BEGIN"); //开始事务
$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row['number']>0){//生成订单 $order_sn=build_order_no();   $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')";  $order_rs=mysql_query($sql,$conn); //库存减少$sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);  if(mysql_affected_rows()){  insertLog('库存减少成功');mysql_query("COMMIT");//事务提交即解锁}else{  insertLog('库存减少失败');}
}else{insertLog('库存不够');mysql_query("ROLLBACK");
}
?>

  优化方案3:使用非阻塞的文件排他锁

<?php
$conn=mysql_connect("localhost","root","123456");
if(!$conn){  echo "connect failed";  exit;
}
mysql_select_db("big-bak",$conn);
mysql_query("set names utf8");$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;//生成唯一订单号
function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){global $conn;$sql="insert into ih_log(event,type) values('$event','$type')";  mysql_query($sql,$conn);
}$fp = fopen("lock.txt", "w+");
if(!flock($fp,LOCK_EX | LOCK_NB)){echo "系统繁忙,请稍后再试";return;
}
//下单
$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row['number']>0){//库存是否大于0//模拟下单操作 $order_sn=build_order_no();    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')";  $order_rs=mysql_query($sql,$conn); //库存减少$sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";$store_rs=mysql_query($sql,$conn);  if(mysql_affected_rows()){  insertLog('库存减少成功');flock($fp,LOCK_UN);//释放锁}else{  insertLog('库存减少失败');}
}else{insertLog('库存不够');
}
fclose($fp);

  优化方案4:使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是)

先将商品库存如队列

<?php
$store=1000;
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$res=$redis->llen('goods_store');
echo $res;
$count=$store-$res;
for($i=0;$i<$count;$i++){$redis->lpush('goods_store',1);
}
echo $redis->llen('goods_store');
?>

  抢购、描述逻辑

<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn){  echo "connect failed";  exit;
}
mysql_select_db("big",$conn);
mysql_query("set names utf8");$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;//生成唯一订单号
function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){global $conn;$sql="insert into ih_log(event,type) values('$event','$type')";  mysql_query($sql,$conn);
}//模拟下单操作
//下单前判断redis队列库存量
$redis=new Redis();
$result=$redis->connect('127.0.0.1',6379);
$count=$redis->lpop('goods_store');
if(!$count){insertLog('error:no store redis');return;
}//生成订单
$order_sn=build_order_no();
$sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
$order_rs=mysql_query($sql,$conn); //库存减少
$sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows()){  insertLog('库存减少成功');
}else{  insertLog('库存减少失败');
}

  模拟5000高并发测试
webbench -c 5000 -t 60 http://192.168.1.198/big/index.php
ab -r -n 6000 -c 5000  http://192.168.1.198/big/index.php

上述只是简单模拟高并发下的抢购,真实场景要比这复杂很多,很多注意的地方
如抢购页面做成静态的,通过ajax调用接口
再如上面的会导致一个用户抢多个,思路:
需要一个排队队列和抢购结果队列及库存队列。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在,则已抢购,否则未抢购,库存减1,写数据库,将用户入结果队列。

测试数据表

--
-- 数据库: `big`
---- ----------------------------------------------------------
-- 表的结构 `ih_goods`
--CREATE TABLE IF NOT EXISTS `ih_goods` (`goods_id` int(10) unsigned NOT NULL AUTO_INCREMENT,`cat_id` int(11) NOT NULL,`goods_name` varchar(255) NOT NULL,PRIMARY KEY (`goods_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;--
-- 转存表中的数据 `ih_goods`
--INSERT INTO `ih_goods` (`goods_id`, `cat_id`, `goods_name`) VALUES
(1, 0, '小米手机');-- ----------------------------------------------------------
-- 表的结构 `ih_log`
--CREATE TABLE IF NOT EXISTS `ih_log` (`id` int(11) NOT NULL AUTO_INCREMENT,`event` varchar(255) NOT NULL,`type` tinyint(4) NOT NULL DEFAULT '0',`addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;--
-- 转存表中的数据 `ih_log`
---- ----------------------------------------------------------
-- 表的结构 `ih_order`
--CREATE TABLE IF NOT EXISTS `ih_order` (`id` int(11) NOT NULL AUTO_INCREMENT,`order_sn` char(32) NOT NULL,`user_id` int(11) NOT NULL,`status` int(11) NOT NULL DEFAULT '0',`goods_id` int(11) NOT NULL DEFAULT '0',`sku_id` int(11) NOT NULL DEFAULT '0',`price` float NOT NULL,`addtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单表' AUTO_INCREMENT=1 ;--
-- 转存表中的数据 `ih_order`
---- ----------------------------------------------------------
-- 表的结构 `ih_store`
--CREATE TABLE IF NOT EXISTS `ih_store` (`id` int(11) NOT NULL AUTO_INCREMENT,`goods_id` int(11) NOT NULL,`sku_id` int(10) unsigned NOT NULL DEFAULT '0',`number` int(10) NOT NULL DEFAULT '0',`freez` int(11) NOT NULL DEFAULT '0' COMMENT '虚拟库存',PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='库存' AUTO_INCREMENT=2 ;--
-- 转存表中的数据 `ih_store`
--INSERT INTO `ih_store` (`id`, `goods_id`, `sku_id`, `number`, `freez`) VALUES
(1, 1, 11, 500, 0);

  

高并发处理【电商抢购】(转)相关推荐

  1. 电商抢购秒杀系统的设计_1_应用场景分析

    2019独角兽企业重金招聘Python工程师标准>>> 电商抢购秒杀系统的设计_1_应用场景分析 概述 所谓知已知彼,百战不殆,在开始详细介绍实战中的抢购秒杀系统时,我们了解一些抢购 ...

  2. 王利芬对话蒲易 ——花店如何成为高端电商?_北京_歇会儿网

    王利芬对话蒲易 --花店如何成为高端电商?_北京_歇会儿网 王利芬对话蒲易 --花店如何成为高端电商?_北京_歇会儿网 王利芬对话蒲易 --花店如何成为高端电商? posted on 2014-06- ...

  3. 如何开发及维护一个可运营性很高的电商系统

    1.  一定要逐步考虑安排分库分表 (1)  用户表.商品表.订单表等基本表必须要考虑分库分表,要不未来随着量大很容易出现异常. (2)  首页.列表页等聚合页面,通过数据冗余,在分库分表后多写一份, ...

  4. 可临摹的PSD分层模板,拆解上线,高逼格电商设计竟如此简单?

    构图分析 电商设计的构图中最常见的就是中心集中式构图. 画面由一个主要元素撑满,主标题作为次要元素配合画面平衡,根据画面需求添加小标签装饰. 整体视觉冲击力强.张力足,适用于单个产品以及细节较为丰富的 ...

  5. 电商抢购高并发JAVA简单实现

    假设场景如下: 库存20瓶茅台53°. 页面发起100万次请求抢购20瓶茅台,用Jmeter测试工具模拟. 要求100万次请求全部得到及时响应,抢购到或者抢购不到. 单机springboot内嵌tom ...

  6. Android电商抢购倒计时,Android限时抢购倒计时实现代码

    限时抢购倒计时实现效果图 布局: android:id="@+id/ll_xsqg" android:layout_width="match_parent" a ...

  7. 完美日记:实现高弹性高稳定电商架构

    公司简介 完美日记(Perfect Diary)是广州市"独角兽"创新企业--广州逸仙电子商务有限公司旗下首个美妆品牌,创立于2017年,用心为新生代女性开发高品质.精设计.易上手 ...

  8. 垂直电商架构进化之路

    作者:张增.邓良驹,分别为乐视云计算电商云团队负责人,乐视云计算高级开发工程师 来自:高效运维 1. 电商系统发展过程 电商网站在不同时期的架构复杂度有所不同: 初创期:商品类型少,业务复杂度低,系统 ...

  9. 乐视电商云的整体架构与技术实现

    本文根据[高效运维社区讲坛]线上活动的分享整理而成. 嘉宾介绍 主题简介 本次分享将带大家了解电商系统的发展过程,并分析在高速发展期的电商面临的问题,同时跟大家分享乐视电商云的架构和实践方案. 1. ...

最新文章

  1. 近 100 场专题演讲、14 大沉浸式应用场景…别不信!这是真·烧脑技术盛会
  2. 经典算法复现!(条件随机场)CRF原理及实现代码
  3. 1+X web中级 Laravel学习笔记——Laravel中的路由
  4. C#中split分隔字符串的应用
  5. Python+pillow计算椭圆图形几何中心
  6. oracle 9企业版,Oracle 9i简体中文企业版【32位amp;564位客户端】 - 网络软件频道 -天天游戏吧...
  7. 11月22日云栖精选夜读:双11享Go了吗?2017阿里双11在线峰会续写科技盛宴!
  8. 漫画分销系统服务器配置,漫画分销平台哪个好?月流水30万的老手来谈谈!
  9. Android仿人人客户端(v5.7.1)——消息中心视图的实现
  10. vit transformer中的cls_token
  11. 不能理解命令行选项 “n”_如何理解命令行
  12. 把笔记本改造成无线路由器 —— 手机抓包牛刀小试
  13. Vivado FPGA基础设计操作流程(1)
  14. 第一篇博客,写给自己
  15. 论文精讲 | 一种隐私保护边云协同训练
  16. 程序员955不加班名单
  17. QString和QDateTime之间相互转化
  18. PHP完成微信小程序在线支付功能
  19. 如何获得的office 365 年卡
  20. 来~打包实现小程序动态分享图一条龙服务( ¨̮ )

热门文章

  1. python如何编程日期_python编程开发之日期操作实例分析
  2. wdatepicker不显示秒_为什么别人电脑开机只要3秒,你有固态硬盘却要等上18秒?...
  3. 10分钟学会数据库压力测试
  4. Fiddler抓取HTTPS最全(强)攻略!
  5. 如何制定自动化测试战略?10年经验让我考虑这些关键要素!
  6. 编译原理第三版清华pdf_清华网络科学与网络空间研究院考研经验分享
  7. activator.createinstance 需要垃圾回收么_Epsilon:你为什么需要一个不回收内存的垃圾回收器?...
  8. rebase in git
  9. python安装和更新pip
  10. android so导致启动慢,谈谈Android NDK中动态链接库(.so文件)的优化