php(TP5)+redis实现秒杀抢购(不限制用户购买次数和限制用户购买次数)(附源码)

不限制用户购买次数

https://www.cnblogs.com/qqlong/p/9327844.html 借鉴地址

最下方有所有代码包和数据库

说明:本次环境测试在Linux下运行

使用的环境、版本、工具

环境:lnmp + redis缓存。
版本:Centos 7、nginx 1.7.4、mysql 5.8、 php 7.3nts、 redis 5.0.6
工具:ab压力测试工具

安装ThinkPHP5.0的最新版本
安装Redis缓存
安装LNMP
Yum安装mysql5.8
Yum安装ab测试工具
配置nginx虚拟域名

不限制用户购买次数

主要代码如下:

#!/bin/bash

/**
 * User: hzbskak
 * Date: 2019/12/25 - 16:23
 */

namespace app\index\controller;


use think\Db;
use think\Exception;

class Seckill
{
    public $price = 10;
    public $user_id = 1;
    public $goods_id = 1;
    public $sku_id   = 11;
    public $number = 1;
    private $redis = null;

    public function __construct()
    {
        //模拟下单操作
        //下单前判断redis队列库存量
        $this->redis=new \Redis();
        $this->redis->connect('127.0.0.1',6379);
    }

    // 主方法
    public function go()
    {
        // 取出用户
        if( !$this->userPop())
        {
            $this->insertLog('no users buy');
            return;
        }

        // 检查库存
        $count=$this->redis->lpop('goods_store');
        if(!$count){
            $this->insertLog($this->user_id .' error:no store redis');
            return;
        }


            //生成订单
            $order_sn=$this->build_order_no();

            $order_rs = Db::name( 'order')
                ->insert([
                    'order_sn' => $order_sn,
                    'user_id' => $this->user_id,
                    'goods_id' => $this->goods_id,
                    'sku_id' => $this->sku_id,
                    'price' => $this->price
                ]);

            //库存减少
            $store_rs = Db::name( 'store')
                ->where( 'sku_id', $this->sku_id)
                ->setDec( 'number', $this->number);
            if($store_rs){
                $this->insertLog($this->user_id .' 库存减少成功');
                return;
            }else{
                $this->insertLog($this->user_id .' 库存减少失败');
                return;
            }

    }

    // 用户取出队列
    // 返回用户id
    public function userPop()
    {
        return $this->user_id = $this->redis->rpoplpush( 'user_line_up', 'user_pop_queue');
    }

    public function user()
    {
        dump( $this->redis->lRange( 'user_line_up', 0, -1));
    }

    public function clear()
    {
        dump( $this->redis->flushDB());
    }

    public function make()
    {
        echo $this->goodsStockAddQueue();
        echo $this->userLineUp();
    }

    public function test()
    {
        dump( $this->redis->keys( '*'));

        dump( $this->redis->lLen( 'user_line_up'));
        dump( $this->redis->lLen( 'goods_store'));
    }

    // 用户排队队列 $i即为user_id
    public function userLineUp()
    {
        // 模拟用户二次下单
        $num = [];
        for ( $a=0;$a<2;$a++)
        {
            for ( $i=1;$i<1001;$i++)
            {
                array_push( $num, $i);
            }
            // 打乱排序
            shuffle( $num);
        }
        for ( $i=0; $i< count( $num); $i++)
        {
            $this->redis->lPush( 'user_line_up', $num[$i]);
        }
        echo '用户排队数:'.$this->redis->lLen( 'user_line_up');
    }


    // 商品库存加入队列
    public function goodsStockAddQueue()
    {
        $store=500;
        $res=$this->redis->llen('goods_store');
        $count=$store-$res;
        for($i=0;$i<$count;$i++){
            $this->redis->lpush('goods_store',1);
        }
        echo '商品剩余库存:'.$this->redis->llen('goods_store');
    }

    //生成唯一订单号
    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){
        Db::name( 'log')
            ->insert( [
                'event' => $event,
                'type' => $type
            ]);
    }
}
测试:

按顺序在网页或者命令行中执行如下代码:
(主要是事先把模拟的用户和商品库存存入缓存中)

http://seckill.ln//index/seckill/clear 
返回  
true

http://seckill.ln//index/seckill/make  
返回 
商品剩余库存:500用户排队数:2000

http://seckill.ln/index/seckill/test  
返回 
array(2) {
  [0] => string(11) "goods_store"
  [1] => string(12) "user_line_up"
}
int(2000)
int(500)
int(0)

测试地址:

http://seckill.ln/index/seckill/go

在命令行中输入:

[root@localhost ~]# ab -r -n 6000 -c 5000  http://seckill.ln/index/seckill/go
# -n代表请求数,-c代表并发数 -r 出现异常不停止,继续执行

如果执行出现如下错误,可以通过修改ab配置来避免错误:
socket: Too many open files (24)

解决该错误的地址

执行结果:
我们可以看到Failed Requests 的值为 5256,不要慌张,这是以为响应的值不同造成了,并不影响。
如果想要去掉,可以把返回值都设为return。

主要看这几个参数:
Time taken for tests: 15.576 seconds 请求时间
Requests per second: 385.20 [#/sec] (mean) 每秒钟请求次数 执行速度还是很乐观的

[root@localhost ~]# ab -r -n 6000 -c 5000  http://seckill.ln/index/seckill/go
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking seckill.ln (be patient)
Completed 600 requests
Completed 1200 requests
Completed 1800 requests
Completed 2400 requests
Completed 3000 requests
Completed 3600 requests
Completed 4200 requests
Completed 4800 requests
Completed 5400 requests
Completed 6000 requests
Finished 6000 requests


Server Software:        nginx
Server Hostname:        seckill.ln
Server Port:            80

Document Path:          /index/seckill/go
Document Length:        0 bytes

Concurrency Level:      5000
Time taken for tests:   15.576 seconds
Complete requests:      6000
Failed requests:        5087
   (Connect: 0, Receive: 0, Length: 5087, Exceptions: 0)
Write errors:           0
Non-2xx responses:      5087
Total transferred:      1771965 bytes
HTML transferred:       864790 bytes
Requests per second:    385.20 [#/sec] (mean)
Time per request:       12980.180 [ms] (mean)
Time per request:       2.596 [ms] (mean, across all concurrent requests)
Transfer rate:          111.09 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0  168 575.7      0    3011
Processing:     1  725 2051.4     13   15316
Waiting:        1  725 2051.5     13   15316
Total:          5  893 2186.6     13   15447

Percentage of the requests served within a certain time (ms)
  50%     13
  66%    191
  75%    501
  80%    865
  90%   3504
  95%   5808
  98%   8273
  99%  10239
 100%  15447 (longest request)

现在我们到数据库中数据是否正确:
是否有同一用户多次下单
是否有超卖现象

库存表 ih_store:

mysql> select * from ih_store;
+----+----------+--------+--------+-------+
| id | goods_id | sku_id | number | freez |
+----+----------+--------+--------+-------+
|  1 |        1 |     11 |      0 |     0 |
+----+----------+--------+--------+-------+
1 row in set (0.02 sec)

number为0,说明库存为空
补充说明: 把库存字段 number 设置为 UNSIGNED 无符号,可以防止库存为负

订单表 ih_order:

mysql> select  COUNT(*) from ih_order;
+----------+
| COUNT(*) |
+----------+
|      500 |
+----------+
1 row in set (0.02 sec)

订单数量是否与库存对应

至此,不限制用户购买次数的代码就算完成了。

代码包地址

数据库:

代码包里根目录下的test.sql就是数据库文件

/*
 Navicat Premium Data Transfer

 Source Server         : ln
 Source Server Type    : MySQL
 Source Server Version : 50728
 Source Host           : 192.168.238.129:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 50728
 File Encoding         : 65001

 Date: 26/12/2019 11:59:26
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for ih_goods
-- ----------------------------
DROP TABLE IF EXISTS `ih_goods`;
CREATE TABLE `ih_goods`  (
  `goods_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `cat_id` int(11) NOT NULL,
  `goods_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`goods_id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of ih_goods
-- ----------------------------
INSERT INTO `ih_goods` VALUES (1, 0, '小米手机');

-- ----------------------------
-- Table structure for ih_log
-- ----------------------------
DROP TABLE IF EXISTS `ih_log`;
CREATE TABLE `ih_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `event` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `type` tinyint(4) NOT NULL DEFAULT 0,
  `addtime` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for ih_order
-- ----------------------------
DROP TABLE IF EXISTS `ih_order`;
CREATE TABLE `ih_order`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_sn` char(32) CHARACTER SET utf8 COLLATE utf8_general_ci 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(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for ih_store
-- ----------------------------
DROP TABLE IF EXISTS `ih_store`;
CREATE TABLE `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`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '库存' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of ih_store
-- ----------------------------
INSERT INTO `ih_store` VALUES (1, 1, 11, 500, 0);

SET FOREIGN_KEY_CHECKS = 1;

你可能感兴趣的:(php)