前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >推荐11-PHP用redis解决超卖的问题

推荐11-PHP用redis解决超卖的问题

作者头像
猿哥
发布2019-09-19 17:18:05
9170
发布2019-09-19 17:18:05
举报
文章被收录于专栏:Web技术布道师

前言

在商品秒杀活动中,比如商品库存只有100,但是在抢购活动中可能有200人同时抢购,这样就出现了并发,在100件商品下单完成库存为0了还有可能继续下单成功,就出现了超卖。

为了解决这个问题,今天我主要讲一下用redis队列的方式处理。redis有list类型,list类型其实就是一个双向链表。通过lpush,pop操作从链表的头部或者尾部添加删除元素。这使得list即可以用作栈,也可以用作队列。先进先出,一端进,一端出,这就是队列。在队列里前一个走完之后,后一个才会走,所以redis的队列能完美的解决超卖并发的问题。

解决秒杀超卖问题的方法还有比如:1.使用mysql的事务加排他锁来解决;2.使用文件锁实现。3.使用redis的setnx来实现锁机制等。

实现原理

将商品库存循环lpush到num里,然后在下单的时候通过rpop每次取出1件商品,当num的值为0时,停止下单。

第1步创建表

一共有三张表,分别是:订单表、商品表、日志表。

1.订单表
代码语言:javascript
复制
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 AUTO_INCREMENT=5820 DEFAULT CHARSET=utf8 COMMENT='订单表'
2.商品表
代码语言:javascript
复制
CREATE TABLE `ims_hotmallstore_goods` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '商品名称',
  `type_id` int(11) NOT NULL COMMENT '商品分类',
  `img` text NOT NULL COMMENT '商品图片',
  `money` decimal(10,2) NOT NULL COMMENT '售价',
  `money2` decimal(10,2) NOT NULL COMMENT '原价',
  `is_show` int(11) NOT NULL DEFAULT '1' COMMENT '1.上架2.下架',
  `uniacid` int(11) NOT NULL COMMENT '小程序id',
  `inventory` int(11) NOT NULL COMMENT '库存',
  `details` text NOT NULL COMMENT '详情',
  `store_id` int(11) NOT NULL COMMENT '商家id',
  `sales` int(11) NOT NULL COMMENT '销量',
  `logo` varchar(100) NOT NULL,
  `num` int(11) NOT NULL,
  `is_gg` int(11) NOT NULL DEFAULT '2' COMMENT '是否开启规格',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
3.日志表
代码语言:javascript
复制
CREATE TABLE `ims_order_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `status` int(11) NOT NULL DEFAULT '0',
  `msg` text CHARACTER SET utf8,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4562 DEFAULT CHARSET=gb2312 COMMENT='订单日志表'

第2步写代码

代码语言:javascript
复制
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;
    }

    // 将商品库存循环到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=pdo_fetch($sql);
        if(!empty($goods)){
         for($i=1; $i<=$goods['num']; $i++){
             $redis->lpush('num',$i);
         }
         die('成功!');
        }else{
         $this->echoMsg(0,'商品不存在。');
        }
    }

    // 抢购下单
    public function doPageGoodsStore()
    {
            $goods_id=1;
            $sql="select id, num, money from ims_hotmallstore_goods where id=".$goods_id;
            $goods=pdo_fetch($sql);
            $redis=self::Redis();
            $count=$redis->rpop('num');//每次从num取出1
            if($count==0){
                $this->echoMsg(0,'no num redis');
            }
            $this->doPageGoodsOrder($goods,1);
            
    }

    // 保存日志
    public function echoMsg($status,$msg,$_data="")
    {
      
        $data=json_encode(array('status'=>$status,'msg'=>$msg,'data'=>$_data),JSON_UNESCAPED_UNICODE);
        $order_log['status']=$status;
        $order_log['msg']=$msg;
        $order_log['create_time']=time();
        pdo_insert('order_log',$order_log);
       die($data);
    }
    public function orderNo()
    {
        return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
    
    // 下单更新库存
    public function doPageGoodsOrder($goods,$goods_number)
    {
        $orderNo=$this->orderNo();
        $number=$goods['num']-$goods_number;
        if($number<0){
            $this->echoMsg(0,'已没有库存');
        }
        $user_id=rand(1,500);
        $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');
        pdo_insert('order',$order);
        $sql="update ims_hotmallstore_goods set num=num-".$goods_number." where num>0 and id=".$goods['id'];
        $res=pdo_query($sql);
        if(!empty($res)){
            $this->echoMsg(1,'库存扣减成功'.$number);
        }
        $redis=self::Redis();
        $redis->lpush('num',$goods_number);
        $this->echoMsg(0,'库存扣减失败'.$number);

    }
 }

// 调用--将商品库存循环到lpush的num里
if($_GET['i']==1){
   $model = new Test;
   $model->doPageSaveNum();
}

// 调用--高并发抢购下单
if($_GET['i']==2){
   $model = new Test;
   $model->doPageGoodsStore();
}

第3步并发测试

1.先手动执行: http://127.0.0.1/wqchunjingsvn/web/index.php?i=1 ,将商品库存循环保存到lpush的num里。

2.这里我用Apache的ab测试,安装方法本文最后做补充。打开终端,然后执行: ab -n 1000 -c 200 http://127.0.0.1/wqchunjingsvn/web/index.php?i=2 (-n发出1000个请求,-c模拟200并发,请求数要大于或等于并发数。相当1000人同时访问,后面是测试url )

3.观察是否执行成功,执行结果如下图,说明执行成功。

第4步查看数据表

1.查看订单表,总订单数量为100,如下图,没问题。

2.查看商品库存,已经由原来的100变成0,也没问题。

3.查看日志表,总共137条记录,其中status为1的只有100条,也没问题。

总结分析

1.方案可行,库存为0,没有出现超卖。

2.用Apache的ab测试高并发时需要注意Url地址不能拼接上带&号的参数,否则执行失败。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP技术大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 实现原理
  • 第1步创建表
    • 1.订单表
      • 2.商品表
        • 3.日志表
        • 第2步写代码
        • 第3步并发测试
        • 第4步查看数据表
        • 总结分析
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档