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,'心情');
  1. 首先安装jmeter压测工具,这是需要java的sdk的,需要在本机电脑下载javasdk并配置环境变量,这里我就不做记录

mysql 微妙 纳秒 mysql秒杀_mysql 微妙 纳秒

  1. 设置访问路径,现在使用的是原生php实现秒杀功能,在下面会对接到tp框架中
  2. mysql 微妙 纳秒 mysql秒杀_sql_02

  3. 编写了一个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);
}
  1. 编写一个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";
  1. 使用压测工具访问serivce方法,进行如果同时并发超过500,会造成什么样的情况。现在是库存数量为

mysql 微妙 纳秒 mysql秒杀_mysql 微妙 纳秒_03

  1. 点击这个开始访问,看看500个同时并发请求会 造成什么效果

mysql 微妙 纳秒 mysql秒杀_php_04

  1. 500个并发请求,造成了超卖,不过就多卖出了两条,还可以,不算太恐怖,但是这是随机的,有可能就会超出很多条,可以使用压测工具多测试几次,但是一定要把库存加上,不然在刚开始哪里就给中断了,就不会在造成超卖了。

mysql 微妙 纳秒 mysql秒杀_mysql 微妙 纳秒_05

  1. 加上排它锁实现防止超卖。加上排它锁可以防止超卖,但是加锁就以为这会有锁等待,并且访问速度会锁下降,
<?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";