php实现仿秒杀功能
前沿: 学习让人上瘾,开始该学习学习并发了,秒杀正好是一个非常好的例子,先使用mysql处理,在使用redis处理。使用jmeter压测工具,测试并发访问500个请求会出现什么样的结果,
数据表
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品id',
`price` decimal(10,2) DEFAULT NULL COMMENT '商品价格',
`num` int(11) DEFAULT NULL COMMENT '库存',
`goods_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '商品名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `water` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '流水记录表',
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '购买用户名',
`price` decimal(10,2) DEFAULT NULL COMMENT '购买商品金额',
`num` int(11) DEFAULT NULL COMMENT '购买数量',
`create_time` int(11) DEFAULT NULL COMMENT '购买时间',
`order_sn` char(30) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '商品订单表',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- 测试添加一条商品数据
insert into goods values(null,200,10,'心情');
- 首先安装jmeter压测工具,这是需要java的sdk的,需要在本机电脑下载javasdk并配置环境变量,这里我就不做记录
- 设置访问路径,现在使用的是原生php实现秒杀功能,在下面会对接到tp框架中
- 编写了一个db.php.用来做数据库的连接请求,使用的是pdo方式连接
<?php
$host = '127.0.0.1';
$user = 'root';
$pwd = 'root';
$dbname = 'test_data';
$dsn = "mysql:host=$host;dbname=$dbname;";
try{
$db = new PDO($dsn,$user,$pwd);
}catch(\PDOException $e){
exit("数据库连接失败,错误原因:".$e->getMessage());
}
# 插入 商品 数据
# $sql = "insert into goods values(null,200,10,'心情')";
# $res = $db->query($sql);
function random()
{
return 'test_'.time().rand(0,100000);
}
- 编写一个service.php来实现秒杀场景
<?php
# 引用db方法
require 'db.php';
# 默认查询一条商品数据,获取价格和数量
$sql = "select * from goods where id = 1";
# 执行sql查询
$data = $db->query($sql);
# 获取一条记录数
$row = $data->fetch();
# 判断当前库存是否大于0
if($row['num'] > 0)
{
# 如果当前sql数量部位空,则执行添加到流水表中
$order_sn = random(); # 生成的订单编号
$name = $row['goods_name']; # 商品名称
$price = $row['price']; # 商品价格
# 购买商品时间
$time = time(); # 购买商品时间
# 默认只减去一次记录
$sql = "insert into water values(null,'{$name}',$price,1,$time,'{$order_sn}')";
$res = $db->query($sql);
# 更新商品表中的商品数量
$up_sql = "update goods set num=num-1 where id = 1";
$upres = $db->query($up_sql);
if($upres){
// 成功
}else{
// 失败
}
}else{
// 库存不够
}
echo "success";
- 使用压测工具访问serivce方法,进行如果同时并发超过500,会造成什么样的情况。现在是库存数量为
- 点击这个开始访问,看看500个同时并发请求会 造成什么效果
- 500个并发请求,造成了超卖,不过就多卖出了两条,还可以,不算太恐怖,但是这是随机的,有可能就会超出很多条,可以使用压测工具多测试几次,但是一定要把库存加上,不然在刚开始哪里就给中断了,就不会在造成超卖了。
- 加上排它锁实现防止超卖。加上排它锁可以防止超卖,但是加锁就以为这会有锁等待,并且访问速度会锁下降,
<?php
require 'db.php';
# pdo开启事务的方法,
$db->beginTransaction();
# 默认查询一条商品数据,获取价格和数量
$sql = "select * from goods where id = 1 for update"; # 加上排它锁
# 执行sql查询
$data = $db->query($sql);
# 获取一条记录数
$row = $data->fetch();
# 判断当前库存是否大于0
if($row['num'] > 0)
{
# 如果当前sql数量部位空,则执行添加到流水表中
$order_sn = random(); # 生成的订单编号
$name = $row['goods_name']; # 商品名称
$price = $row['price']; # 商品价格
# 购买商品时间
$time = time(); # 购买商品时间
# 默认只减去一次记录
$sql = "insert into water values(null,'{$name}',$price,1,$time,'{$order_sn}')";
$res = $db->query($sql);
# 更新商品表中的商品数量
$up_sql = "update goods set num=num-1 where id = 1";
$upres = $db->query($up_sql);
# 提交本次查询
$db->commit();
if($upres){
// 成功
echo "success1";
}else{
// 失败
}
}else{
// 库存不够
}
echo "success";