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

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

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要请戳这里链接 或 者关注咱们下面的知乎专栏

PHP架构师圈子​zhuanlan.zhihu.com

php生成 sku_高并发下,php与redis实现的抢购、秒杀功能相关推荐

  1. 简单实现redis实现高并发下的抢购/秒杀功能

    简述 抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢? 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大 ...

  2. java redis实现抢购_【抢购/秒杀】redis实现高并发下的抢购/秒杀功能

    问题: 抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢? 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否 ...

  3. 高并发下如何生成随机数

    在平时的开发中我们经常会用到随机数,比如使用new Random().Math.random()等生成,然而在高并发环境中(比如电商项目,中间件系统等)使用上面的方法并不是最优的,会影响系统性能.那么 ...

  4. php mysql 秒杀_redis+PHP实现高并发下秒杀数据入库的问题

    在高并发下实现抢购秒杀功能中,我有一个疑问,就是数据入库的问题,什么时候入库. 设想思路: 1.判断他抢购成功了,立马把生成的订单数据写入mysql订单表,同时库存表字段减少1: 2.判断抢购成功后, ...

  5. 面试官:高并发下,如何保证分布式唯一全局 ID 生成?

    欢迎关注方志朋的博客,回复"666"获面试宝典 前言 系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结. 这篇文章就是给各位看官提供一个生成分布式唯一 ...

  6. mysql存入订单号不重复_MSSQL高并发下生成连续不重复的订单号

    一.确定需求 只要做过开发的基本上都有做过订单,只要做过订单的基本上都要涉及生成订单号,可能项目订单号生成规则都不一样,但是大多数规则都是连续增长. 所以假如给你一个这样的需求,在高并发下,以天为单位 ...

  7. 高并发下生成订单号的策略

    臭味相投的朋友们,我在这里: 猿in小站:http://www.yuanin.net csdn博客:https://blog.csdn.net/jiabeis 简书:https://www.jians ...

  8. 高并发下秒杀商品,你必须知道的9个细节

    前言 高并发下如何设计秒杀系统?这是一个高频面试题.这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识. 秒杀一般出现在商城的促销活动中,指定了一定数量(比如:10个 ...

  9. 高并发下如何保证接口的幂等性?

    前言 接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题.本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考. 不知道你有没有遇到过这些场景: ...

最新文章

  1. 解决Ubuntu 14.04 Unity桌面环境登录后冻结问题
  2. .net framework 注册到IIS上
  3. 这5个机器学习项目你不可错过!(附代码)
  4. haproxy+keepalived应用实战
  5. Python:图片懒加载技术
  6. Linux双网卡绑定
  7. WeihanLi.Npoi 1.7.0 更新介绍
  8. MySQL-01-linux安装MySQL的两种方式及其遇到的问题
  9. Android studio导入工程很卡及下载网络jar很慢问题总结
  10. 品牌类软文经典案例分享,深刻了解软文营销的“魅力”
  11. scratch 3 下载和安装
  12. 计算机网络课程设计——校园网的规划与设计
  13. android全屏/沉浸式状态栏下,各种键盘挡住输入框解决办法
  14. django数据库错误django. db. utils. OperationalError: ( 1044,‘Access denied for user’ erqueque’@' %’to‘dj’
  15. 2.SPSS入门基础——数据录入数据管理编程规则图表可视化
  16. python如何绘制饼图_Python使用Plotly绘图工具,绘制饼图
  17. 第十四届蓝桥杯要开始了(2022年)
  18. 如何正确创建电子邮件通讯(逐步)
  19. 简单版的相似图片搜索原理
  20. Xcode 12 Beta 3 更新记录SwiftUI2

热门文章

  1. java 并发 线程安全_Java并发教程–线程安全设计
  2. jvm jinfo 参数_jinfo:JVM运行时配置的命令行浏览
  3. Java代理初学者指南
  4. Vaadin Flow –奇妙的鹿
  5. moxy json介绍_MOXy是GlassFish 4中新的默认JSON绑定提供程序
  6. apache hadoop_春天遇见Apache Hadoop
  7. xtend怎么使用_使用Xtend构建Vaadin UI
  8. java更好的语言_Java,如果这是一个更好的世界
  9. activemq 内存_ActiveMQ:了解内存使用情况
  10. mapreduce 聚合_MapReduce:处理数据密集型文本处理–局部聚合第二部分