PHP uses redis to solve the problem of oversold

Time:2020-8-7

preface

In the event of the second killing of goods, for example, the inventory of goods is only 100, but in the rush purchase activity, 200 people may rush to buy at the same time, so there is concurrency. If the order of 100 goods is completed and the inventory is 0, it may continue to place an order successfully, and there will be oversold.

In order to solve this problem, today I will mainly talk about using the redis queue. Redis has a list type, which is actually a two-way linked list. Add and delete elements from the head or tail of the linked list through lpush and pop operations. This allows the list to be used either as a stack or as a queue. First in, first out, one end in, one end out, this is the queue. In the queue, the first one will go after the last one, so the queue of redis can perfectly solve the problem of oversold concurrency.

There are also ways to solve the problem: 1. Using MySQL transaction exclusive lock to solve; 2. Using file lock. 3. Use the setnx of redis to implement the locking mechanism. Check and click: four ways to avoid oversold

Implementation principle

The product inventory is cycled to num by lpush, and then one product is taken out each time through rpop when the order is placed. When the value of num is 0, the order is stopped.

Step 1 create the table

There are three tables: order table, commodity table and log table.

1. Order form

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 'price: unit: Min',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_ Increment = 5820 default charset = utf8 comment ='order table '

2. Commodity list

CREATE TABLE `ims_hotmallstore_goods` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `Name ` varchar (50) not null comment 'commodity name',
  `type_ ID ` int (11) not null comment 'commodity classification',
  `IMG ` text not null comment 'product picture',
  `Money ` decimal (10,2) not null comment 'selling price',
  `Money2 ` decimal (10,2) not null comment 'original price',
  `is_ Show ` int (11) not null default '1' comment '1,
  `Uniacid ` int (11) not null comment 'applet ID',
  `Inventory ` int (11) not null comment 'inventory',
  `Details ` text not null comment 'details',
  `store_ ID ` int (11) not null comment 'merchant ID',
  `Sales ` int (11) not null comment 'sales',
  `logo` varchar(100) NOT NULL,
  `num` int(11) NOT NULL,
  `is_ GG ` int (11) not null default '2' comment 'enable specification',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

3. Log table

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 ='order log table '

Step 2 write the code

class Test {

    private static $instance = null;

    //Instantiating redis in single column mode
    public static function Redis()
    {
        if (self::$instance == null) {
            $redis=new \Redis();
            $redis->connect('127.0.0.1',6379);
            self::$instance = $redis;
        }
        return self::$instance;
    }

    //Cycle the inventory of goods to the num of lpush
    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, 'product does not exist. ).
        }
    }

    //Rush to order
    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 '); // take 1 from num each time
            if($count==0){
                $this->echoMsg(0,'no num redis');
            }
            $this->doPageGoodsOrder($goods,1);
            
    }

    //Save log
    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);  
    }
    
    //Order update inventory
    public function doPageGoodsOrder($goods,$goods_number)
    {   
        $orderNo=$this->orderNo();
        $number=$goods['num']-$goods_number;
        if($number<0){
            $this - > echomsg (0, 'no inventory');
        }
        $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 of inventory deduction succeeded ');
        }
        $redis=self::Redis();
        $redis->lpush('num',$goods_number);
        $this - > echomsg (0, inventory deduction failed. $number);

    }
 }

//Call -- to cycle the inventory of goods into num of lpush
if($_GET['i']==1){
   $model = new Test;
   $model->doPageSaveNum();
}

//Call -- high concurrency rush order
if($_GET['i']==2){
   $model = new Test;
   $model->doPageGoodsStore();
}

Step 3 concurrent test

1. Manually execute:http://127.0.0.1/wqchunjingsvn/web/index.php?i=1And save the inventory cycle to the num of lpush.

2. Here I use Apache AB test and installation method to make a supplement at the end of this article. Open the terminal and execute:ab -n 1000 -c 200 http://127.0.0.1/wqchunjingsvn/web/index.php?i=2
(- N sends 1000 requests, – C simulates 200 concurrency, and the number of requests should be greater than or equal to the number of concurrent requests. 1000 people visit at the same time, followed by the test URL)

3. Observe whether the execution is successful. The execution result is shown in the figure below, which indicates that the execution is successful.

PHP uses redis to solve the problem of oversold

Step 4 view the data table

1. Check the order table, the total order quantity is 100, as shown in the figure below, no problem.

PHP uses redis to solve the problem of oversold

2. Check the inventory of goods. It has changed from 100 to 0, which is no problem.

PHP uses redis to solve the problem of oversold

3. Check the log table. There are 137 records in total, of which only 100 records have status of 1.

PHP uses redis to solve the problem of oversold

PHP uses redis to solve the problem of oversold

Summary and analysis

1. The scheme is feasible, the inventory is 0, and there is no oversold.

2. When using Apache AB to test high concurrency, it is necessary to note that the URL address cannot be spliced with the parameter with & sign, otherwise the execution will fail.

Related information

Using redis to solve the problem of overselling PHP
How to solve the oversold problem of high concurrency seconds
Mac install Apache HTTP server (test with Apache AB, installation method)
Detailed explanation of pressure test with ab
MySQL concurrent update

Recommended Today

How to share queues with hypertools 2.5

Share queue with swote To realize asynchronous IO between processes, the general idea is to use redis queue. Based on the development of swote, the queue can also be realized through high-performance shared memory table. Copy the code from the HTTP tutorial on swoole’s official website, and configure four worker processes to simulate multiple producers […]