日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

php 用redis实现限时抢购,并且防止超卖和重复购买

發(fā)布時間:2024/1/3 综合教程 24 生活家
生活随笔 收集整理的這篇文章主要介紹了 php 用redis实现限时抢购,并且防止超卖和重复购买 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

在商品秒殺活動中,比如商品庫存只有100,但是在搶購活動中可能有200人同時搶購,這樣就出現(xiàn)了并發(fā),在100件商品下單完成庫存為0了還有可能繼續(xù)下單成功,就出現(xiàn)了超賣。

為了解決這個問題,今天我主要講一下用redis隊列的方式處理。redis有l(wèi)ist類型,list類型其實就是一個雙向鏈表。通過lpush,pop操作從鏈表的頭部或者尾部添加刪除元素。這使得list即可以用作棧,也可以用作隊列。先進先出,一端進,一端出,這就是隊列。在隊列里前一個走完之后,后一個才會走,所以redis的隊列能完美的解決超賣并發(fā)的問題。

解決秒殺超賣問題的方法還有比如:1.使用mysql的事務(wù)加排他鎖來解決;2.使用文件鎖實現(xiàn)。3.使用redis的setnx來實現(xiàn)鎖機制等。

實現(xiàn)原理

將商品庫存循環(huán)lpush到num里,然后在下單的時候通過rpop每次取出1件商品,當(dāng)num的值為0時,停止下單。

第1步創(chuàng)建表

一共有三張表,分別是:訂單表、商品表、日志表。

1.訂單表

CREATE TABLE `ims_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',
  `number` int(11) NOT NULL,
  `price` int(10) NOT NULL COMMENT '價格:單位為分',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';

2.商品表

CREATE TABLE `ims_hotmallstore_goods`  (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品名稱',
  `money` decimal(10, 2) NOT NULL COMMENT '售價',
  `sales` int(11) NOT NULL COMMENT '銷量',
  `num` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of ims_hotmallstore_goods
-- ----------------------------
INSERT INTO `ims_hotmallstore_goods` VALUES (1, '商品1', 1000.00, 10, 10);

第2步代碼

<?php
header("Content-type:text/html;charset=utf-8");
class MyPDO {
    protected static $_instance = null;
    protected $dbName = '';
    protected $dsn;
    protected $dbh;
    /**
  * 構(gòu)造
  *
  * @return MyPDO
  */
    private function __construct($dbHost, $dbUser, $dbPasswd, $dbName, $dbCharset) {
        try {
            $this->dsn = 'mysql:host='.$dbHost.';dbname='.$dbName;
            $this->dbh = new PDO($this->dsn, $dbUser, $dbPasswd);
            $this->dbh->exec('SET character_set_connection='.$dbCharset.', character_set_results='.$dbCharset.', character_set_client=binary');
        }
        catch (PDOException $e) {
            exit($e->getMessage());
        }
    }
    /**
  * 防止克隆
  *
  */
    private function __clone() {
    }
    /**
  * Singleton instance
  *
  * @return Object
  */
    public static function getInstance($dbHost, $dbUser, $dbPasswd, $dbName, $dbCharset) {
        if (self::$_instance === null) {
            self::$_instance = new self($dbHost, $dbUser, $dbPasswd, $dbName, $dbCharset);
        }
        return self::$_instance;
    }
    /**
  * Query 查詢
  */
    public function query($strSql, $queryMode = 'All', $debug = false) {
        if ($debug === true) $this->debug($strSql);
        $recordset = $this->dbh->query($strSql);
        if ($recordset) {
            $recordset->setFetchMode(PDO::FETCH_ASSOC);
            if ($queryMode == 'All') {
                $result = $recordset->fetchAll();
            } elseif ($queryMode == 'Row') {
                $result = $recordset->fetch();
            }
        } else {
            $result = null;
        }
        return $result;
    }
    /**
  * Insert 插入
  */
    public function insert($table, $arrayDataValue, $debug = false) {
        $strSql = "INSERT INTO `$table` (`".implode('`,`', array_keys($arrayDataValue))."`) VALUES ('".implode("','", $arrayDataValue)."')";
        if ($debug === true) $this->debug($strSql);
        $result = $this->dbh->exec($strSql);
        return $result;
    }
    /**
  * 執(zhí)行語句
  */
    public function execSql($strSql, $debug = false) {
        if ($debug === true) $this->debug($strSql);
        $result = $this->dbh->exec($strSql);
        return $result;
    }
    /**
  * debug
  *
  * @param mixed $debuginfo
  */
    private function debug($debuginfo) {
        var_dump($debuginfo);
        exit();
    }
}
class Test {
    private static $instance = null;
    // 用單列模式 實例化Redis
    public static function Redis() {
        if (self::$instance == null) {
            $redis=new Redis();
            $redis->connect('127.0.0.1',6379);
            self::$instance = $redis;
        }
        return self::$instance;
    }
    public function getOne($sql) {
        $db = MyPDO::getInstance('localhost', 'root', '168168', 'test', 'utf8');
        $data = $db->query($sql)[0];
        return $data;
    }
    public function exec($sql) {
        $db = MyPDO::getInstance('localhost', 'root', '168168', 'test', 'utf8');
        return $db->execSql($sql);
    }
    public function insert($table,$data) {
        $db = MyPDO::getInstance('localhost', 'root', '168168', 'test', 'utf8');
        return $db->insert($table,$data);
    }
    // 將商品庫存循環(huán)到lpush的num里
    public function doPageSaveNum() {
        $redis=self::Redis();
        $goods_id=1;
        $sql="select id, num,money from ims_hotmallstore_goods where id=".$goods_id;
        $goods = $this->getOne($sql);
        //print_r($goods);die;
        if(!empty($goods['num'])) {
            for ($i=1; $i<=$goods['num']; $i++) {
                $redis->lpush('num',$i);
            }
            die('成功!庫存數(shù):'.$goods['num']);
        } else {
            die('數(shù)據(jù)庫已無庫存');
        }
    }
    // 搶購下單
    public function doPageGoodsStore() {
        $redis=self::Redis();
        $goods_id=1;
        $user_id = mt_rand(1,100);
        if ($redis->sismember('user_list_'.$goods_id,$user_id)) {
            echo '已下單';
            return false;
            ;
        }
        $count=$redis->rpop('num');
        //每次從num取出1,防止超賣
        if($count==0) {
            $this->echoMsg(0,'已無庫存');
        }
        //加入已購買用戶集合,防止重復(fù)購買
        $redis->sAdd('user_list_'.$goods_id,$user_id);
        $sql="select id, num, money from ims_hotmallstore_goods where id=".$goods_id;
        $goods = $this->getOne($sql);
        $this->doPageGoodsOrder($user_id,$goods,1);
    }
    public function orderNo() {
        return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
    // 下單更新庫存
    public function doPageGoodsOrder($user_id,$goods,$goods_number) {
        $orderNo=$this->orderNo();
        $number=$goods['num']-$goods_number;
        if($number<0) {
            $this->echoMsg(0,'已沒有庫存');
        }
        //mysql判斷已購買用戶  (自行處理)
        //...
        $order['user_id']=$user_id;
        $order['goods_id']=$goods['id'];
        $order['number']=$goods_number;
        $order['price']=$goods['money'];
        $order['status']=1;
        $order['sku_id']=2;
        $order['order_sn']=$orderNo;
        $order['create_time']=date('Y-m-d H:i:s');
        $this->insert('ims_order',$order);
        $sql="update ims_hotmallstore_goods set num=num-".$goods_number." where num>0 and id=".$goods['id'];
        $res=$this->exec($sql);
        // echo $sql;die;
        if(!empty($res)) {
            echo "庫存扣減成功,庫存剩下:$number".PHP_EOL;
            return false;
        } else {
            $redis=self::Redis();
            $redis->lpush('num',$goods_number);
            //扣庫存失敗,把庫存加回
            $redis->SREM('user_list_'.$goods_id,$user_id);
            //已購買用戶集合移除
            $this->echoMsg(0,'庫存扣減失敗');
        }
    }
    // 保存日志
    public function echoMsg($status,$msg,$exit = true) {
        if($exit == true) {
            die($msg);
        } else {
            echo $msg;
        }
    }
}
if(!isset($_GET['i'])) {
    exit('缺失參數(shù)i');
}
// 調(diào)用--將商品庫存循環(huán)到lpush的num里
if($_GET['i']==1) {
    $model = new Test;
    $model->doPageSaveNum();
}
// 調(diào)用--高并發(fā)搶購下單
if($_GET['i']==2) {
    $model = new Test;
    $model->doPageGoodsStore();
}
if($_GET['i']==3) {
    $model = new Test;
    for ($i=1; $i<=100; $i++) {
        $model->doPageGoodsStore();
    }
}
//http://127.0.0.1/qianggou/test.php?i=1
// ab -n 2000 -c 500  http://127.0.0.1/qianggou/test.php?i=2
// (-n發(fā)出2000個請求,-c模擬500并發(fā),請求數(shù)要大于或等于并發(fā)數(shù)。相當(dāng)2000人同時訪問,后面是測試url )

第3步并發(fā)測試

1.先手動執(zhí)行:http://127.0.0.1/web/index.php?i=1,將商品庫存循環(huán)保存到lpush的num里。

2.這里我用Apache的ab測試,安裝方法本文最后做補充。打開終端,然后執(zhí)行:ab -n 1000 -c 200 http://127.0.0.1/web/index.php?i=2
(-n發(fā)出1000個請求,-c模擬200并發(fā),請求數(shù)要大于或等于并發(fā)數(shù)。相當(dāng)1000人同時訪問,后面是測試url )

3.查看數(shù)據(jù)是否超發(fā)

總結(jié)

以上是生活随笔為你收集整理的php 用redis实现限时抢购,并且防止超卖和重复购买的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。