Home  >  Article  >  Backend Development  >  PHP combines with redis to realize rush purchase, flash sale functions and optimization solutions

PHP combines with redis to realize rush purchase, flash sale functions and optimization solutions

零到壹度
零到壹度Original
2018-03-23 10:41:282420browse

Rush buying and flash sales are a very common application scenario nowadays. There are two main problems that need to be solved: one is the pressure caused by high concurrency on the database, and the other is how to solve the correct inventory reduction under competition ( "oversold" problem). For the first question, it is already easy to think of using cache to handle rush purchases and avoid directly operating the database, such as using Redis.

The focus is on the second question

Conventional writing method:

Query the corresponding product Inventory, see if it is greater than 0, and then perform operations such as generating an order. However, when judging whether the inventory is greater than 0, there will be problems under high concurrency, resulting in a negative inventory number


<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn){
    echo "connect failed";
    exit;
}
mysql_select_db("big",$conn);
mysql_query("set names utf8");
 
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
 
//生成唯一订单
function build_order_no(){
  return date(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type)
    values(&#39;$event&#39;,&#39;$type&#39;)";
    mysql_query($sql,$conn);
}
 
//模拟下单操作
//库存是否大于0
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39;";//解锁 此时ih_store数据中goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; 的数据被锁住(注3),其它事务必须等待此次事务 提交后才能执行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row[&#39;number&#39;]>0){//高并发下会导致超卖
    $order_sn=build_order_no();
    //生成订单
    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
    values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)";
    $order_rs=mysql_query($sql,$conn);
     
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysql_query($sql,$conn);
    if(mysql_affected_rows()){
        insertLog(&#39;库存减少成功&#39;);
    }else{
        insertLog(&#39;库存减少失败&#39;);
    }
}else{
    insertLog(&#39;库存不够&#39;);
}
?>

Optimization plan 1:

##Set the inventory field number field to unsigned. When the inventory is 0, because the field cannot be a negative number, false will be returned

<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 0);"></span>

优化方案2:

使用MySQL的事务,锁住操作的行

<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn){
    echo "connect failed";
    exit;
}
mysql_select_db("big",$conn);
mysql_query("set names utf8");
 
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
 
//生成唯一订单号
function build_order_no(){
  return date(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type)
    values(&#39;$event&#39;,&#39;$type&#39;)";
    mysql_query($sql,$conn);
}
 
//模拟下单操作
//库存是否大于0
mysql_query("BEGIN");   //开始事务
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row[&#39;number&#39;]>0){
    //生成订单
    $order_sn=build_order_no();
    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
    values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)";
    $order_rs=mysql_query($sql,$conn);
     
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysql_query($sql,$conn);
    if(mysql_affected_rows()){
        insertLog(&#39;库存减少成功&#39;);
        mysql_query("COMMIT");//事务提交即解锁
    }else{
        insertLog(&#39;库存减少失败&#39;);
    }
}else{
    insertLog(&#39;库存不够&#39;);
    mysql_query("ROLLBACK");
}
?>

优化方案3:

使用非阻塞的文件排他锁

<?php
$conn=mysql_connect("localhost","root","123456");
if(!$conn){
    echo "connect failed";
    exit;
}
mysql_select_db("big-bak",$conn);
mysql_query("set names utf8");
 
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
 
//生成唯一订单号
function build_order_no(){
  return date(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type)
    values(&#39;$event&#39;,&#39;$type&#39;)";
    mysql_query($sql,$conn);
}
 
$fp = fopen("lock.txt", "w+");
if(!flock($fp,LOCK_EX | LOCK_NB)){
    echo "系统繁忙,请稍后再试";
    return;
}
//下单
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39;";
$rs=mysql_query($sql,$conn);
$row=mysql_fetch_assoc($rs);
if($row[&#39;number&#39;]>0){//库存是否大于0
    //模拟下单操作
    $order_sn=build_order_no();
    $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
    values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)";
    $order_rs=mysql_query($sql,$conn);
     
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysql_query($sql,$conn);
    if(mysql_affected_rows()){
        insertLog(&#39;库存减少成功&#39;);
        flock($fp,LOCK_UN);//释放锁
    }else{
        insertLog(&#39;库存减少失败&#39;);
    }
}else{
    insertLog(&#39;库存不够&#39;);
}
fclose($fp);


优化方案4:

使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是)

先将商品库存如队列

<?php
$store=1000;
$redis=new Redis();
$result=$redis->connect(&#39;127.0.0.1&#39;,6379);
$res=$redis->llen(&#39;goods_store&#39;);
echo $res;
$count=$store-$res;
for($i=0;$i<$count;$i++){
    $redis->lpush(&#39;goods_store&#39;,1);
}
echo $redis->llen(&#39;goods_store&#39;);
?>

抢购、描述逻辑

<?php
$conn=mysql_connect("localhost","big","123456");
if(!$conn){
    echo "connect failed";
    exit;
}
mysql_select_db("big",$conn);
mysql_query("set names utf8");
 
$price=10;
$user_id=1;
$goods_id=1;
$sku_id=11;
$number=1;
 
//生成唯一订单号
function build_order_no(){
  return date(&#39;ymd&#39;).substr(implode(NULL, array_map(&#39;ord&#39;, str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}
//记录日志
function insertLog($event,$type=0){
    global $conn;
    $sql="insert into ih_log(event,type)
    values(&#39;$event&#39;,&#39;$type&#39;)";
    mysql_query($sql,$conn);
}
 
//模拟下单操作
//下单前判断redis队列库存量
$redis=new Redis();
$result=$redis->connect(&#39;127.0.0.1&#39;,6379);
$count=$redis->lpop(&#39;goods_store&#39;);
if(!$count){
    insertLog(&#39;error:no store redis&#39;);
    return;
}
 
//生成订单
$order_sn=build_order_no();
$sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;)";
$order_rs=mysql_query($sql,$conn);
 
//库存减少
$sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows()){
    insertLog(&#39;库存减少成功&#39;);
}else{
    insertLog(&#39;库存减少失败&#39;);

上述只是简单模拟高并发下的抢购,真实场景要比这复杂很多,以后会一一跟大家分享。

1

2

##3

4

5

6

//库存减少
$sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39; and number>0";
$store_rs=mysql_query($sql,$conn);
if(mysql_affected_rows()){
    insertLog(&#39;库存减少成功&#39;);
}

<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 0);"></span>

The above is the detailed content of PHP combines with redis to realize rush purchase, flash sale functions and optimization solutions. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn