ホームページ  >  記事  >  バックエンド開発  >  PHP での同時実行条件が高い場合に製品在庫が売れすぎないようにする方法

PHP での同時実行条件が高い場合に製品在庫が売れすぎないようにする方法

WBOY
WBOY転載
2022-04-06 10:37:107708ブラウズ

この記事では、PHP に関する関連知識を提供します。主に、高同時実行条件下での製品在庫の過剰販売の防止に関する関連問題を紹介し、主にデータベース上の高同時実行によって引き起こされる問題を解決します。プレッシャーと競争の下で売れすぎた在庫を解決する方法について、あらゆる人に役立ちます。

PHP での同時実行条件が高い場合に製品在庫が売れすぎないようにする方法

この記事に基づくテストは、「php 高同時実行テスト: 在庫の過剰販売防止に関するケーススタディ」からご覧いただけます。 " 場合。 [推奨学習: "PHP チュートリアル "]

モール システムでは、飛び込み販売やフラッシュ セールが非常に一般的なマーケティング シナリオです。一定期間内に、大量ののユーザーがモールを訪れて注文を行っています。解決する必要がある主な問題は 2 つあります:

  • データベースの同時実行性の高さによって生じるプレッシャー;

  • 競争条件下での製品在庫のオーバーフローの問題を解決する方法販売;

データベースでの高い同時実行性によって引き起こされるプレッシャー#1 つ目の問題については、キャッシュを使用して対処します。Redis を使用するなど、データベースを直接操作することは避けてください。

競争下で商品の売れすぎを解決する方法2 番目の質問については、説明に重点を置く必要があります。

従来の書き方:該当する商品の在庫を問い合わせ、在庫数が0より大きいかどうかを判断し、発注などの操作を行っていましたが、在庫が0より大きいかどうかを判断する際に、同時実行性が高いと問題が発生し、その結果、在庫レベルがマイナスになります。

テーブル sql をテストします次のテーブル データをデータベースにインポートします

/*
Navicat MySQL Data Transfer

Source Server         : 01 本地localhost
Source Server Version : 50553
Source Host           : localhost:3306
Source Database       : test

Target Server Type    : MYSQL
Target Server Version : 50553
File Encoding         : 65001

Date: 2020-11-06 14:31:35
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for products
-- ----------------------------
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
  `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `title` varchar(50) DEFAULT NULL COMMENT '货品名称',
  `store` int(11) DEFAULT '0' COMMENT '货品库存',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='货品表';

-- ----------------------------
-- Records of products
-- ----------------------------
INSERT INTO `products` VALUES ('1', '稻花香大米', '20');

-- ----------------------------
-- Table structure for order_log
-- ----------------------------
DROP TABLE IF EXISTS `order_log`;
CREATE TABLE `order_log` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `content` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '日志内容',
  `c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

-- ----------------------------
-- Table structure for order
-- ----------------------------
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `oid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '订单号',
  `product_id` int(11) DEFAULT '0' COMMENT '商品ID',
  `number` int(11) DEFAULT '0' COMMENT '购买数量',
  `c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`oid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT='订单表';

注文処理コード

<?php

db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

//step2 查询商品信息
$sql = "select * from products where id={$product_id}";
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
//此处在高并发下,可能出现上一个下单后还没来得及更新库存,下一个下单判断库存数不是最新的库存
if ($row[&#39;store&#39;] > 0) {

    sleep(1);
    //step4 更新商品库存数量(减去下单数量)
    $sql = "update products set store=store-{$buy_num} where id={$product_id}";
    if (mysqli_query($con, $sql)) {
        echo "更新成功";
        //step5 生成订单号创建订单
        $oid = build_order_no();
        create_order($oid, $product_id, $buy_num);
        insertLog(&#39;库存减少成功,下单成功&#39;);
    } else {
        echo "更新失败";
        insertLog(&#39;库存减少失败&#39;);
    }

} else {
    echo "没有库存";
    insertLog(&#39;库存不够&#39;);
}

function db()
{
    global $con;
    $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
    if (!$con) {
        echo "数据库连接失败";
    }
}

/**
 * 生成唯一订单号
 */
function build_order_no()
{
    return date(&#39;Ymd&#39;) . str_pad(mt_rand(1, 99999), 5, &#39;0&#39;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
    global $con;
    $sql = "INSERT INTO `order` (oid, product_id, number) values(&#39;$oid&#39;, &#39;$product_id&#39;, &#39;$number&#39;)";
    mysqli_query($con, $sql);
}

/**
 * 记录日志
 */
function insertLog($content)
{
    global $con;
    $sql = "INSERT INTO `order_log` (content) values(&#39;$content&#39;)";
    mysqli_query($con, $sql);
}

在庫フィールドフィールドを符号なしに設定します在庫フィールドには負の数値を指定できないため, 注文後 商品在庫を更新する際、負の数値が表示された場合は false が返されます

#
<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

//step2 查询商品信息
$sql = "select * from products where id={$product_id} for UPDATE";//利用for update 开启行锁
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
if ($row[&#39;store&#39;] > 0) {

    sleep(1);
    //step4 更新商品库存数量(减去下单数量)
    $sql = "update products set store=store-{$buy_num} where id={$product_id}";
    if (mysqli_query($con, $sql)) {
        echo "更新成功";
        //step5 生成订单号创建订单
        $oid = build_order_no();
        create_order($oid, $product_id, $buy_num);
        insertLog(&#39;库存减少成功,下单成功&#39;);
    } else {
        // 如果出现负数将返回false
        echo "更新失败";
        insertLog(&#39;库存减少失败&#39;);
    }
} else {
    //商品已经抢购完
    echo "没有库存";
    insertLog(&#39;库存不够&#39;);
}

function db()
{
    global $con;
    $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
    if (!$con) {
        echo "数据库连接失败";
    }
}

/**
 * 生成唯一订单号
 */
function build_order_no()
{
    return date(&#39;Ymd&#39;) . str_pad(mt_rand(1, 99999), 5, &#39;0&#39;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
    global $con;
    $sql = "INSERT INTO `order` (oid, product_id, number) values(&#39;$oid&#39;, &#39;$product_id&#39;, &#39;$number&#39;)";
    mysqli_query($con, $sql);
}

/**
 * 记录日志
 */
function insertLog($content)
{
    global $con;
    $sql = "INSERT INTO `order_log` (content) values(&#39;$content&#39;)";
    mysqli_query($con, $sql);
}

#mysql トランザクションを使用して操作の行をロック

#注文処理プロセスでは、mysql トランザクションを使用して、注文を行っている製品の行データをロックします。

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

mysqli_query($con, "BEGIN"); //开始事务

//step2 查询商品信息
$sql = "select * from products where id={$product_id} for UPDATE";//利用for update 开启行锁
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
if ($row[&#39;store&#39;] > 0) {

    sleep(1);
    //step4 更新商品库存数量(减去下单数量)
    $sql = "update products set store=store-{$buy_num} where id={$product_id}";
    if (mysqli_query($con, $sql)) {
        echo "更新成功";
        //step5 生成订单号创建订单
        $oid = build_order_no();
        create_order($oid, $product_id, $buy_num);
        insertLog(&#39;库存减少成功,下单成功&#39;);
        mysqli_query($con, "COMMIT");//事务提交即解锁
    } else {
        echo "更新失败";
        insertLog(&#39;库存减少失败&#39;);
        mysqli_query($con, "ROLLBACK");//事务回滚即解锁
    }
} else {
    //商品已经抢购完
    echo "没有库存";
    insertLog(&#39;库存不够&#39;);
    mysqli_query($con, "ROLLBACK");//事务回滚即解锁
}

function db()
{
    global $con;
    $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
    if (!$con) {
        echo "数据库连接失败";
    }
}

/**
 * 生成唯一订单号
 */
function build_order_no()
{
    return date(&#39;Ymd&#39;) . str_pad(mt_rand(1, 99999), 5, &#39;0&#39;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
    global $con;
    $sql = "INSERT INTO `order` (oid, product_id, number) values(&#39;$oid&#39;, &#39;$product_id&#39;, &#39;$number&#39;)";
    mysqli_query($con, $sql);
}

/**
 * 记录日志
 */
function insertLog($content)
{
    global $con;
    $sql = "INSERT INTO `order_log` (content) values(&#39;$content&#39;)";
    mysqli_query($con, $sql);
}

#ノンブロッキング ファイル排他ロックを使用します。

注文の処理中 リクエストを行うとき、flock を使用してファイルをロックします。ロックが失敗した場合は、他の注文が処理中であることを意味します。このとき、ユーザーは待つか、待つ必要があります。ユーザーに「サーバーがビジーです」というメッセージを直接表示し、データベースへのクエリを回避するために、カウンターには購入した製品の数が保存されます。 ブロッキング (待機) モード: 同時実行中に 2 番目のユーザー要求があると、プログラムは最初のユーザー要求が完了するまで待機し、ロックを解放し、ファイル ロックを取得してからプログラムが続行します。走る。

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

$fp = fopen(&#39;lock.txt&#39;, &#39;w&#39;);
if (flock($fp, LOCK_EX)) {   //文件独占锁,阻塞
    //step2 查询商品信息
    $sql = "select * from products where id={$product_id}";
    $result = mysqli_query($con, $sql);
    $row = mysqli_fetch_assoc($result);

    //step3 判断商品下单数量是否大于商品库存数量
    if ($row[&#39;store&#39;] > 0) {
        //处理订单
        sleep(1);
        //step4 更新商品库存数量(减去下单数量)
        $sql = "update products set store=store-{$buy_num} where id={$product_id}";
        if (mysqli_query($con, $sql)) {
            echo "更新成功";
            //step5 生成订单号创建订单
            $oid = build_order_no();
            create_order($oid, $product_id, $buy_num);
            insertLog(&#39;库存减少成功,下单成功&#39;);
        } else {
            echo "更新失败";
            insertLog(&#39;库存减少失败&#39;);
        }
    } else {
        //商品已经抢购完
        echo "没有库存";
        insertLog(&#39;库存不够&#39;);
    }
    flock($fp, LOCK_UN); //释放锁

}
fclose($fp);

function db()
{
    global $con;
    $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
    if (!$con) {
        echo "数据库连接失败";
    }
}

/**
 * 生成唯一订单号
 */
function build_order_no()
{
    return date(&#39;Ymd&#39;) . str_pad(mt_rand(1, 99999), 5, &#39;0&#39;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
    global $con;
    $sql = "INSERT INTO `order` (oid, product_id, number) values(&#39;$oid&#39;, &#39;$product_id&#39;, &#39;$number&#39;)";
    mysqli_query($con, $sql);
}

/**
 * 记录日志
 */
function insertLog($content)
{
    global $con;
    $sql = "INSERT INTO `order_log` (content) values(&#39;$content&#39;)";
    mysqli_query($con, $sql);
}
ノンブロッキング モード: 同時実行中に、最初のユーザーがファイル ロックを要求して取得します。後で直接リクエストしたユーザーはシステム ビジー状態に戻ります。後でもう一度お試しください

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

$fp = fopen(&#39;lock.txt&#39;, &#39;w&#39;);
if (flock($fp, LOCK_EX|LOCK_NB)) {   //文件独占锁,非阻塞
    //step2 查询商品信息
    $sql = "select * from products where id={$product_id}";
    $result = mysqli_query($con, $sql);
    $row = mysqli_fetch_assoc($result);

    //step3 判断商品下单数量是否大于商品库存数量
    if ($row[&#39;store&#39;] > 0) {
        //处理订单
        sleep(1);
        //step4 更新商品库存数量(减去下单数量)
        $sql = "update products set store=store-{$buy_num} where id={$product_id}";
        if (mysqli_query($con, $sql)) {
            echo "更新成功";
            //step5 生成订单号创建订单
            $oid = build_order_no();
            create_order($oid, $product_id, $buy_num);
            insertLog(&#39;库存减少成功,下单成功&#39;);
        } else {
            echo "更新失败";
            insertLog(&#39;库存减少失败&#39;);
        }
    } else {
        //商品已经抢购完
        echo "没有库存";
        insertLog(&#39;库存不够&#39;);
    }
    flock($fp, LOCK_UN); //释放锁

} else {
    //系统繁忙,请稍后再试
    echo "系统繁忙,请稍后再试";
    insertLog(&#39;系统繁忙,请稍后再试&#39;);
}
fclose($fp);

function db()
{
    global $con;
    $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
    if (!$con) {
        echo "数据库连接失败";
    }
}

/**
 * 生成唯一订单号
 */
function build_order_no()
{
    return date(&#39;Ymd&#39;) . str_pad(mt_rand(1, 99999), 5, &#39;0&#39;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
    global $con;
    $sql = "INSERT INTO `order` (oid, product_id, number) values(&#39;$oid&#39;, &#39;$product_id&#39;, &#39;$number&#39;)";
    mysqli_query($con, $sql);
}

/**
 * 记录日志
 */
function insertLog($content)
{
    global $con;
    $sql = "INSERT INTO `order_log` (content) values(&#39;$content&#39;)";
    mysqli_query($con, $sql);
}

Redis キューを使用します

理由操作はアトミックです。多くのユーザーが同時に到着した場合でも、それらは順番に実行されます。

  • mysql トランザクションのパフォーマンスは、高い環境下では大幅に低下します。


    ##1. まず製品在庫を Redis キューに入れます
  • <?php
    
    db();
    global $con;
    
    // 查询商品信息
    $product_id = 1;
    $sql = "select * from products where id={$product_id}";
    $result = mysqli_query($con, $sql);
    $row = mysqli_fetch_assoc($result);
    $store = $row[&#39;store&#39;];
    
    // 获取商品在redis缓存的库存
    $redis = new Redis();
    $result = $redis->connect(&#39;127.0.0.1&#39;, 6379);
    $key = &#39;goods_store_&#39; . $product_id;
    $res = $redis->llen($key);
    $count = $store - $res;
    
    for ($i=0; $i<$count; $i++) {
        $redis->lpush($key, 1);
    }
    echo $redis->llen($key);
    
    function db()
    {
        global $con;
        $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
        if (!$con) {
            echo "数据库连接失败";
        }
    }
  • 2. 急ぎ購入とフラッシュ セールのロジック

    <?php
    
    db();
    global $con;
    
    //step1 接收下单参数
    $product_id = 1;// 商品ID
    $buy_num = 1;// 购买数量
    
    //step2 下单前判断redis队列库存量
    $redis = new Redis();
    $result = $redis->connect(&#39;127.0.0.1&#39;,6379);
    $count = $redis->lpop(&#39;goods_store_&#39; . $product_id);
    if (!$count) {
        insertLog(&#39;error:no store redis&#39;);
        return &#39;秒杀结束,没有商品库存了&#39;;
    }
    
    sleep(1);
    //step3 更新商品库存数量(减去下单数量)
    $sql = "update products set store=store-{$buy_num} where id={$product_id}";
    if (mysqli_query($con, $sql)) {
        echo "更新成功";
        //step4 生成订单号创建订单
        $oid = build_order_no();
        create_order($oid, $product_id, $buy_num);
        insertLog(&#39;库存减少成功,下单成功&#39;);
    } else {
        echo "更新失败";
        insertLog(&#39;库存减少失败&#39;);
    }
    
    function db()
    {
        global $con;
        $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
        if (!$con) {
            echo "数据库连接失败";
        }
    }
    
    /**
     * 生成唯一订单号
     */
    function build_order_no()
    {
        return date(&#39;Ymd&#39;) . str_pad(mt_rand(1, 99999), 5, &#39;0&#39;, STR_PAD_LEFT);
    }
    
    function create_order($oid, $product_id, $number)
    {
        global $con;
        $sql = "INSERT INTO `order` (oid, product_id, number) values(&#39;$oid&#39;, &#39;$product_id&#39;, &#39;$number&#39;)";
        mysqli_query($con, $sql);
    }
    
    /**
     * 记录日志
     */
    function insertLog($content)
    {
        global $con;
        $sql = "INSERT INTO `order_log` (content) values(&#39;$content&#39;)";
        mysqli_query($con, $sql);
    }

redis オプティミスティック ロック防止の過剰販売

<?php

$redis =new Redis();
$redis->connect("127.0.0.1", 6379);
$redis->watch(&#39;sales&#39;);//乐观锁 监视作用 set()  初始值0
$sales = $redis->get(&#39;sales&#39;);

$n = 20;// 库存
if ($sales >= $n) {
    exit(&#39;秒杀结束&#39;);
}

//redis开启事务
$redis->multi();
$redis->incr(&#39;sales&#39;); //将 key 中储存的数字值增一 ,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
$res = $redis->exec(); //成功1 失败0

if ($res) {
    //秒杀成功
    $con = new mysqli(&#39;localhost&#39;,&#39;root&#39;,&#39;root&#39;,&#39;test&#39;);
    if (!$con) {
        echo "数据库连接失败";
    }

    $product_id = 1;// 商品ID
    $buy_num = 1;// 购买数量
    sleep(1);

    $sql = "update products set store=store-{$buy_num} where id={$product_id}";

    if (mysqli_query($con, $sql)) {
        echo "秒杀完成";
    }

} else {
    exit(&#39;抢购失败&#39;);
}

推奨学習: 「

PHP ビデオ チュートリアル

以上がPHP での同時実行条件が高い場合に製品在庫が売れすぎないようにする方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。