抢购、秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:

1 高并发对数据库产生的压力

2 竞争状态下如何解决库存的正确减少(”超卖”问题)

对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。

重点在于第二个问题的解决

常规写法:

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

//生成唯一订单

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的事务,锁住操作的行

//生成唯一订单号

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:使用非阻塞的文件排他锁

//生成唯一订单号

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事务在高并发下性能下降很厉害,文件锁的方式也是)

先将商品库存如队列

1.先将商品库存如队列

$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

$redis->lpush('goods_store',1);

}

echo $redis->llen('goods_store');

?>

2.抢购、描述逻辑

//生成唯一订单号

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('库存减少失败');

}

上述只是简单模拟高并发下的抢购,真实场景要比这复杂很多,很多注意的地方

如抢购页面做成静态的,通过ajax调用接口

再如上面的会导致一个用户抢多个,思路:

需要一个排队队列和抢购结果队列及库存队列。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在,则已抢购,否则未抢购,库存减1,写数据库,将用户入结果队列。

测试数据表

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);

PHP收费事件导致用户流失,PHP秒杀系统方案(解决大流量,高并发)相关推荐

  1. 如何估算代码量_千万级用户的大型网站,应该如何设计其高并发架构?(彩蛋)...

    目录 (1)单块架构 (2)初步的高可用架构 (3)千万级用户量的压力预估 (4)服务器压力预估 (5)业务垂直拆分 (6)用分布式缓存抗下读请求 (7)基于数据库主从架构做读写分离 (8)总结 本文 ...

  2. 【在线网课】Java高性能高并发秒杀系统方案优化实战

    java教程视频讲座简介: Java高性能高并发秒杀系统方案优化实战 Java秒杀系统方案优化 高性能高并发实战 以"秒杀"这一Java高性能高并发的试金石场景为例,带你通过一系列 ...

  3. RocketMQ消息中间件(六下):订单秒杀系统压力过大+再造订单系统专门处理秒杀+MQ中的push+pull的区别

    前言 吃的苦中苦,也不一定是人上人,但是想要泡洋妞,就得有点洋货,小鸡汤喝一碗,撸起袖子干: 链接: rocketMQ(6上)中解决了订单系统的三个问题,那么还剩下一些问题,慢慢来一步步的解决和优化: ...

  4. JAVA秒杀mysql层实现_Java商城高并发秒杀系统架构分析设计与开发实战

    课程大纲 1-1课程整体介绍.mp4 1-2核心技术列表.mp4 1-3课程要求与收益.mp4 1-4系统的整体演示.mp4 2-1微服务项目的搭建-SpringBoot搭建多模块项目一.mp4 2- ...

  5. 高并发秒杀系统方案的优化

    最近接触了一个关于高并发秒杀的项目,在这里稍微整理一下关于这个项目的一些值得记录的一些点,以下是源码地址:github 高并发项目的瓶颈主要在于数据库访问次数上,访问次数越多,对数据库压力也就越大,因 ...

  6. java系统优化方案_Java秒杀系统方案优化 高性能高并发实战-一号门

    类别: 视频 语言: Java 发布日期: 2019-03-02 介绍:以"秒杀"这一Java高性能高并发的试金石场景为例,带你通过一系列系统级优化,学会应对高并发. 第1章 课程 ...

  7. Java秒杀系统方案优化 高性能高并发实战 学习笔记

    秒杀系统 (一)搭建环境 自定义封装Result类 自定义封装CodeMsg类 集成redis和rabbit 封装RedisService类 断言和日志测试 (二)实现用户登录和分布式Session ...

  8. 每天近百亿条用户数据,携程大数据高并发应用架构涅槃

    互联网二次革命的移动互联网时代,如何吸引用户.留住用户并深入挖掘用户价值,在激烈的竞争中脱颖而出,是各大电商的重要课题.通过各类大数据对用户进行研究,以数据驱动产品是解决这个课题的主要手段,携程的大数 ...

  9. Java秒杀系统方案优化【第三方登录】

    一.qq登录 1.申请开发者账号 2.依赖 <dependency><groupId>org.apache.commons</groupId><artifac ...

最新文章

  1. python函数只有被调用才会执行_Python函数调用
  2. Java_apply_in_automatic_system
  3. xamarin ios html5 video.js 无法播放
  4. python怎么读取pdf为文本_python怎么读取pdf文本内容
  5. python使用ElementTree解析XML文件
  6. linux c进程和线程脑图,进程和线程
  7. 搞AI,他的薪资是你的2倍,大概率是因为你没有读这几本书
  8. 缺失值处理 - 拉格朗日插值法 - Python代码
  9. SSL 3.0曝出Poodle漏洞的解决方案-----开发者篇(转自:http://blog.csdn.net/lyq8479/article/details/40709175)...
  10. matlab2c使用c++实现matlab函数系列教程-rand函数
  11. Android自定义样式
  12. 大连工业大学计算机专硕调剂,2020大连工业大学调剂信息
  13. 输出调节2.3——内模控制器设计
  14. 【bat】bat批处理文件的注释
  15. 支付宝企业转账到个人账户
  16. linux c语言lzma,LZMA 算法简介
  17. 《ANSYS 14.0超级学习手册》一1.1 有限元法概述
  18. Cadence 应用注意事项--转载
  19. 如何阅读一份上市公司财报 - 财报阅读入门
  20. 可穿戴式柔性电子应变传感器基底材料

热门文章

  1. gridview中的种种超级链接
  2. 带你初窥谷歌TV的硬软之秘
  3. PVSCSI还是LSI logic?VM SCSI控制器驱动的选择
  4. web developer tips (29):在web应用项目里启用“编辑并继续”功能
  5. ShartPoin无法创建门户网站的问题
  6. devops基础扫盲篇_在2020年取得成功的8篇必读的DevOps文章
  7. 这本关于Node.js的书,是一本神书,助你学会Node.js,为你升职加薪,走上人生巅峰
  8. MyBatis 解析运行原理
  9. Bootstrap列表组的情景类
  10. Bootstrap 滚动监听插件Scrollspy 的事件