Home > Database > Mysql Tutorial > Analyzing flash sale ideas and data security under high concurrency

Analyzing flash sale ideas and data security under high concurrency

怪我咯
Release: 2017-04-05 11:18:42
Original
1455 people have browsed it

We usually measure the throughput rate of a Web system by QPS (Query Per Second, number of requests processed per second). This indicator is very critical to solve high-concurrency scenarios of tens of thousands of times per second. For example, we assume that the average response time for processing a business request is 100ms. At the same time, there are 20 Apache web servers in the system, and MaxClients is configured as 500 (indicating the maximum number of Apache connections).

Then, the theoretical peak QPS of our Web system is (idealized calculation method):

20*500/0.1 = 100000 (100,000 QPS)

Huh? Our system seems to be very powerful. It can handle 100,000 requests in one second. The flash sale of 5w/s seems to be a "paper tiger". The actual situation is of course not so ideal. In actual high-concurrency scenarios, machines are under high load, and the average response time will be greatly increased at this time.

As far as the web server is concerned, the more connection processes Apache opens, the more context switches the CPU needs to handle, which increases the CPU consumption and directly leads to an increase in the average response time. Therefore, the above-mentioned number of MaxClients must be considered based on hardware factors such as CPU and memory. More is definitely not better. You can test it through Apache's own abench and get a suitable value. Then, we choose Redis for storage at the memory operation level. In a high-concurrency state, the storage response time is crucial. Although network bandwidth is also a factor, such request packets are generally relatively small and rarely become a bottleneck for requests. It is rare for load balancing to become a system bottleneck, so we will not discuss it here.

Then the question comes. Assume that our system, in a high concurrency state of 5w/s, the average response time changes from 100ms to 250ms (actual situation, even more):

20 *500/0.25 = 40000 (40,000 QPS)

So, our system is left with 40,000 QPS. Facing 50,000 requests per second, there is a difference of 10,000.

For example, at a highway intersection, 5 cars come and pass 5 cars per second, and the highway intersection operates normally. Suddenly, only 4 cars can pass through this intersection in one second, and the traffic flow is still the same. As a result, there will definitely be a traffic jam. (It feels like 5 lanes suddenly turned into 4 lanes)

Similarly, in a certain second, 20*500 available connection processes are working at full capacity, but there are still 10,000 new requests. , there is no connection process available, and it is expected that the system will fall into an abnormal state.

Analyzing flash sale ideas and data security under high concurrency

In fact, in normal non-high-concurrency business scenarios, similar situations occur. There is a problem with a certain business request interface, and the response time is extremely slow. The entire Web request The response time is very long, gradually filling up the number of available connections on the web server, and no connection process is available for other normal business requests.

The more frightening problem is that it is the behavioral characteristics of users. The more unavailable the system is, the more frequent users click. The vicious cycle eventually leads to an "avalanche" (one of the web machines hangs up, causing the traffic to be dispersed to On other machines that are working normally, the normal machines will also hang, and then a vicious circle will occur), bringing down the entire Web system.

3. Restart and overload protection

If an "avalanche" occurs in the system, restarting the service rashly will not solve the problem. The most common phenomenon is that after starting up, it hangs up immediately. At this time, it is best to deny traffic at the ingress layer and then restart. If services like redis/memcache are also down, you need to pay attention to "warming up" when restarting, and it may take a long time.

In flash sale and rush sale scenarios, the traffic is often beyond our system’s preparation and imagination. At this time, overload protection is necessary. Denying requests is also a protective measure if a full system load condition is detected. Setting up filtering on the front-end is the simplest way, but this approach is a behavior that is "criticized" by users. It is more appropriate to set overload protection at the CGI entry layer to quickly return direct requests from customers

Data security under high concurrency

We know that when multiple threads write the same file (Multiple threads run the same piece of code at the same time. If the result of each run is the same as that of a single-thread run, and the result is the same as expected, it is thread-safe). If it is a MySQL database, you can use its own lock mechanism to solve the problem. However, in large-scale concurrency scenarios, MySQL is not recommended. There is another problem in flash sale and rush sale scenarios, which is "over-delivery". If this aspect is not controlled carefully, excessive delivery will occur. We have also heard that some e-commerce companies conduct rush buying activities. After the buyer successfully purchases the product, the merchant does not recognize the order as valid and refuses to deliver the goods. The problem here may not necessarily be that the merchant is treacherous, but that it is caused by the risk of over-issuance at the technical level of the system.

1. Reasons for excessive hair

Assume that in a certain rush-buying scenario, we only have 100 products in total. At the last moment, we have consumed 99 products and only the last one is left. At this time, the system sent multiple concurrent requests. The product balances read by these requests were all 99, and then they all passed this balance judgment, which eventually led to over-issuance. (Same as the scene mentioned earlier in the article)

Analyzing flash sale ideas and data security under high concurrency

In the picture above, it resulted in concurrent user B also "successfully buying", allowing one more person to obtain the product. This scenario is very easy to occur in high concurrency situations.

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

<?php
//优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
include(&#39;./mysql.php&#39;);
$username = &#39;wang&#39;.rand(0,1000);
//生成唯一订单
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,$username){
    global $conn;
    $sql="insert into ih_log(event,type,usernma)
    values(&#39;$event&#39;,&#39;$type&#39;,&#39;$username&#39;)";
    return mysqli_query($conn,$sql);
}
function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number)
{
      global $conn;
      $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number)
      values(&#39;$order_sn&#39;,&#39;$user_id&#39;,&#39;$goods_id&#39;,&#39;$sku_id&#39;,&#39;$price&#39;,&#39;$username&#39;,&#39;$number&#39;)";
     return  mysqli_query($conn,$sql);
}
//模拟下单操作
//库存是否大于0
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; ";
$rs=mysqli_query($conn,$sql);
$row = $rs->fetch_assoc();
  if($row[&#39;number&#39;]>0){//高并发下会导致超卖
      if($row[&#39;number&#39;]<$number){
        return insertLog(&#39;库存不够&#39;,3,$username);
      }
      $order_sn=build_order_no();
      //库存减少
      $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39; and number>0";
      $store_rs=mysqli_query($conn,$sql);
      if($store_rs){
          //生成订单
          insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number);
          insertLog(&#39;库存减少成功&#39;,1,$username);
      }else{
          insertLog(&#39;库存减少失败&#39;,2,$username);
      }
  }else{
      insertLog(&#39;库存不够&#39;,3,$username);
  }


?>
Copy after login

2. Pessimistic lock idea

There are many ideas for solving thread safety, and we can start the discussion from the direction of "pessimistic locking".

Pessimistic lock, that is, when modifying data, the lock state is adopted to exclude modifications from external requests. When encountering a locked state, you must wait.

Analyzing flash sale ideas and data security under high concurrency

Although the above solution does solve the problem of thread safety, don't forget that our scenario is "high concurrency". In other words, there will be many such modification requests, and each request needs to wait for a "lock". Some threads may never have a chance to grab this "lock", and such requests will die there. At the same time, there will be many such requests, which will instantly increase the average response time of the system. As a result, the number of available connections will be exhausted and the system will fall into an exception.

Optimization plan 2: Use MySQL transactions to lock the operated rows

<?php
//优化方案2:使用MySQL的事务,锁住操作的行
include(&#39;./mysql.php&#39;);
//生成唯一订单号
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;)";
    mysqli_query($conn,$sql);
}

//模拟下单操作
//库存是否大于0
mysqli_query($conn,"BEGIN");  //开始事务
$sql="select number from ih_store where goods_id=&#39;$goods_id&#39; and sku_id=&#39;$sku_id&#39; FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
$rs=mysqli_query($conn,$sql);
$row=$rs->fetch_assoc();
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=mysqli_query($conn,$sql);
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs=mysqli_query($conn,$sql);
    if($store_rs){
      echo &#39;库存减少成功&#39;;
        insertLog(&#39;库存减少成功&#39;);
        mysqli_query($conn,"COMMIT");//事务提交即解锁
    }else{
      echo &#39;库存减少失败&#39;;
        insertLog(&#39;库存减少失败&#39;);
    }
}else{
  echo &#39;库存不够&#39;;
    insertLog(&#39;库存不够&#39;);
    mysqli_query($conn,"ROLLBACK");
}
?>
Copy after login

3. FIFO queue idea

Okay, then let’s slightly modify the above scenario, We directly put the request into the queue and use FIFO (First Input First Output, first in first out). In this way, we will not cause some requests to never obtain the lock. Seeing this, does it feel like forcibly turning multi-threading into single-threading?

Analyzing flash sale ideas and data security under high concurrency

Then, we have now solved the lock problem, and all requests are processed in a "first in, first out" queue. Then a new problem comes. In a high-concurrency scenario, because there are many requests, the queue memory may be "exploded" in an instant, and then the system will fall into an abnormal state. Or designing a huge memory queue is also a solution. However, the speed at which the system processes requests in a queue cannot be compared with the number of crazy influxes into the queue. In other words, the number of requests in the queue will accumulate more and more, and eventually the average response time of the Web system will still drop significantly, and the system will still fall into an exception.

4. The idea of ​​file lock

For applications where the daily IP is not high or the number of concurrency is not very large, generally there is no need to consider this! There is no problem at all with normal file manipulation methods. But if the concurrency is high, when we read and write files, it is very likely that multiple processes will operate on the next file. If we do not monopolize the access to the file at this time, it will easily cause data loss

Optimization plan 4: Use non-blocking file exclusive locks

<?php
//优化方案4:使用非阻塞的文件排他锁
include (&#39;./mysql.php&#39;);
//生成唯一订单号
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;)";
    mysqli_query($conn,$sql);
}


$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 =  mysqli_query($conn,$sql);
$row = $rs->fetch_assoc();
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 =  mysqli_query($conn,$sql);
    //库存减少
    $sql="update ih_store set number=number-{$number} where sku_id=&#39;$sku_id&#39;";
    $store_rs =  mysqli_query($conn,$sql);
    if($store_rs){
      echo &#39;库存减少成功&#39;;
        insertLog(&#39;库存减少成功&#39;);
        flock($fp,LOCK_UN);//释放锁
    }else{
      echo &#39;库存减少失败&#39;;
        insertLog(&#39;库存减少失败&#39;);
    }
}else{
  echo &#39;库存不够&#39;;
    insertLog(&#39;库存不够&#39;);
}
fclose($fp);


 ?>
Copy after login

5. Optimistic locking idea

At this time, we can discuss the idea of ​​"optimistic locking". Optimistic locking adopts a more relaxed locking mechanism compared to "pessimistic locking", and most of them use version updates. The implementation is that all requests for this data are eligible to be modified, but a version number of the data will be obtained. Only those with a consistent version number can be updated successfully, and other requests will be returned to snap-up failure. In this case, we do not need to consider the queue issue, but it will increase the computational overhead of the CPU. However, overall, this is a better solution.

Analyzing flash sale ideas and data security under high concurrency

There are many software and services that support the "optimistic locking" function, such as watch in Redis is one of them. With this implementation, we ensure data security.

Optimization plan 5: watch in Redis

<?php

$redis = new redis();
 $result = $redis->connect(&#39;127.0.0.1&#39;, 6379);
 echo $mywatchkey = $redis->get("mywatchkey");

/*
  //插入抢购数据
 if($mywatchkey>0)
 {
     $redis->watch("mywatchkey");
  //启动一个新的事务。
    $redis->multi();
   $redis->set("mywatchkey",$mywatchkey-1);
   $result = $redis->exec();
   if($result) {
      $redis->hSet("watchkeylist","user_".mt_rand(1,99999),time());
      $watchkeylist = $redis->hGetAll("watchkeylist");
        echo "抢购成功!<br/>"; 
        $re = $mywatchkey - 1;   
        echo "剩余数量:".$re."<br/>";
        echo "用户列表:<pre class="brush:php;toolbar:false">";
        print_r($watchkeylist);
   }else{
      echo "手气不好,再抢购!";exit;
   }  
 }else{
     // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");
     //  $watchkeylist = $redis->hGetAll("watchkeylist");
        echo "fail!<br/>";    
        echo ".no result<br/>";
        echo "用户列表:<pre class="brush:php;toolbar:false">";
      //  var_dump($watchkeylist);  
 }*/


$rob_total = 100;   //抢购数量
if($mywatchkey<=$rob_total){
    $redis->watch("mywatchkey");
    $redis->multi(); //在当前连接上启动一个新的事务。
    //插入抢购数据
    $redis->set("mywatchkey",$mywatchkey+1);
    $rob_result = $redis->exec();
    if($rob_result){
         $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey);
        $mywatchlist = $redis->hGetAll("watchkeylist");
        echo "抢购成功!<br/>";
     
        echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";
        echo "用户列表:<pre class="brush:php;toolbar:false">";
        var_dump($mywatchlist);
    }else{
          $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),&#39;meiqiangdao&#39;);
        echo "手气不好,再抢购!";exit;
    }
}
?>
Copy after login


The above is the detailed content of Analyzing flash sale ideas and data security under high concurrency. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
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
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template