PHP commodities spike solutions to problems and redis example explanation [mysql]

This paper describes examples of PHP commodities spike solutions to problems. Share to you for your reference, as follows:

introduction

Num is assumed that the field is stored in the database, the stored remaining quantity spike product.

IF ($ NUM> 0) { 
  // user buying successful, the user information recorded 
  $ num--; 
}

  

Assuming a higher amount of a concurrent scenario, the database 1 num value, a plurality of processes may simultaneously be read into num is 1, the program is determined eligible, snapping successful, num minus one. This will lead to super-fat product, it had only 10 can buy the goods, there may be more than 10 individual grab, this time snapping num after completion negative.

Solution to this problem by lot, and can be simply divided based on the redis mysql solutions, due to mysql redis performance, it can carry higher concurrency, but the embodiment described below and are based on the redis single mysql higher concurrency needs of distributed solutions, this article does not.

Mysql-based solutions

Commodity goods table

CREATE TABLE `goods` (
 `id` int(11) NOT NULL,
 `num` int(11) DEFAULT NULL,
 `version` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Results Table snapped log

CREATE TABLE `log` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `good_id` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Pessimistic locking

Pessimistic locking scheme is used exclusively to read, that is, at the same time only one process reads the value of num. After the transaction is committed or rolled back, the lock is released, other processes can read. The most straightforward embodiment, at the time of performance is not required, the program can be directly used. Note that, SELECT ... FOR UPDATE to use the index as much as possible, in order to lock the number of lines as little as possible; exclusive lock is released after the end of the transaction was executed, not read it released after the completion of the transaction and therefore should be used earlier submitted or roll back as much as possible, so that early release exclusive lock.

$this->mysqli->begin_transaction();
$result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1 FOR UPDATE");
$row = $result->fetch_assoc();
$num = intval($row['num']);
if($num > 0){
  usleep(100);
  $this->mysqli->query("UPDATE goods SET num=num-1");
  $affected_rows = $this->mysqli->affected_rows;
  if($affected_rows == 1){
    $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");
    $affected_rows = $this->mysqli->affected_rows;
    if($affected_rows == 1){
      $this->mysqli->commit();
      echo "success:".$num;
    }else{
      $this->mysqli->rollback();
      echo "fail1:".$num;
    }
  }else{
    $this->mysqli->rollback();
    echo "fail2:".$num;
  }
}else{
  $this->mysqli->commit();
  echo "fail3:".$num;
}

Optimistic locking

Optimistic locking schemes are not read data plus exclusive lock, but by every update a version from the growing fields to address multiple processes read the same num, then the problem can be updated successfully. Num read in each process, but also read the value of the version, and the version is also updated at the same update num, plus determination of equivalent version in the updating. Suppose there are 10 processes read the value of num 1, version of 9, the update statement execution of these 10 processes are UPDATE goods SET num=num-1,version=version+1 WHERE version=9, however, a process which when executed successfully, the value version of the database will become 10, the remaining nine processes are not executed successfully, thus ensuring that the goods will not super, num's value is not less than 0, but it also leads to a problem that snapped a request issued earlier users may not rush to, but was grabbed subsequent requests.

$result = $this->mysqli->query("SELECT num,version FROM goods WHERE id=1 LIMIT 1");
$row = $result->fetch_assoc();
$num = intval($row['num']);
$version = intval($row['version']);
if($num > 0){
  usleep(100);
  $this->mysqli->begin_transaction();
  $this->mysqli->query("UPDATE goods SET num=num-1,version=version+1 WHERE version={$version}");
  $affected_rows = $this->mysqli->affected_rows;
  if($affected_rows == 1){
    $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");
    $affected_rows = $this->mysqli->affected_rows;
    if($affected_rows == 1){
      $this->mysqli->commit();
      echo "success:".$num;
    }else{
      $this->mysqli->rollback();
      echo "fail1:".$num;
    }
  }else{
    $this->mysqli->rollback();
    echo "fail2:".$num;
  }
}else{
  echo "fail3:".$num;
}

where conditions (atomic operation)

Pessimistic locking scheme to ensure that the values ​​in the database num can only be read and processed at the same time a process, that is concurrent reading process here to line up in sequence. Although optimistic locking scheme value of num can be simultaneously read multiple processes, but the update operation to determine the version of the equivalent guarantees concurrent update operations at the same time only one update was successful.

There is a simpler solution, only plus num> 0 limited to conditions when an update operation. By where conditions are similar programs, though seemingly optimistic locking and programs, are able to prevent the emergence of super-fat problem, but the performance in the large num still very different. After this time If num is 10, while the five processes read num = 10, due to the optimistic locking protocol version field equivalence determination, which will only have a five processes update is successful, the process performed to complete the 5 9 num; where conditions for determining the programs, as long num> 0 can be successfully updated, the process performed after the completion of the five num 5.

$result = $this->mysqli->query("SELECT num FROM goods WHERE id=1 LIMIT 1");
$row = $result->fetch_assoc();
$num = intval($row['num']);
if($num > 0){
  usleep(100);
  $this->mysqli->begin_transaction();
  $this->mysqli->query("UPDATE goods SET num=num-1 WHERE num>0");
  $affected_rows = $this->mysqli->affected_rows;
  if($affected_rows == 1){
    $this->mysqli->query("INSERT INTO log(good_id) VALUES({$num})");
    $affected_rows = $this->mysqli->affected_rows;
    if($affected_rows == 1){
      $this->mysqli->commit();
      echo "success:".$num;
    }else{
      $this->mysqli->rollback();
      echo "fail1:".$num;
    }
  }else{
    $this->mysqli->rollback();
    echo "fail2:".$num;
  }
}else{
  echo "fail3:".$num;
}

Redis-based solutions

Based on watch optimistic locking scheme

for monitoring a watch (or more) Key, if this (or these) are altered Key command before another transaction execution, the transaction will be interrupted. This approach is similar to mysql with the optimistic locking scheme, specific performance is the same.

$num = $this->redis->get('num');
if($num > 0) {
  $this->redis->watch('num');
  usleep(100);
  $res = $this->redis->multi()->decr('num')->lPush('result',$num)->exec();
  if($res == false){
    echo "fail1";
  }else{
    echo "success:".$num;
  }
}else{
  echo "fail2";
}

Based on the program list of the queue

Utilizes atomicity redis dequeue queue-based program, before the first start panic buying product number into the response queue, in turn eject operation from the queue when buying, so you can ensure that each product can only be a process to obtain and operation, super situation does not exist. The advantage of this scheme is to understand and implement are relatively simple, drawbacks when the number of items is large, a large amount of data needs to be stored in a queue, and the need to deposit different products into different message queues.

public function init(){
  $this->redis->del('goods');
  for($i=1;$i<=10;$i++){
    $this->redis->lPush('goods',$i);
  }
  $this->redis->del('result');
  echo 'init done';
}
public function run(){
  $goods_id = $this->redis->rPop('goods');
  usleep(100);
  if($goods_id == false) {
    echo "fail1";
  }else{
    $res = $this->redis->lPush('result',$goods_id);
    if($res == false){
      echo "writelog:".$goods_id;
    }else{
      echo "success".$goods_id;
    }
  }
}

decr scheme based on the return value

If we set num remaining amount as a key type, each judge after the first get, then decr can not solve the problem of super-fat. But redis in decr operation will return results after the execution, can solve the problem of super-fat. We first get to the first step in determining the value of num, num avoid each time to update the value, and then to perform num decr operation, and the return value decr, if the return value is not less than 0, indicating that before decr greater than 0, the user buy success.

public function run(){
  $num = $this->redis->get('num');
  if($num > 0) {
    usleep(100);
    $retNum = $this->redis->decr('num');
    if($retNum >= 0){
      $res = $this->redis->lPush('result',$retNum);
      if($res == false){
        echo "writeLog:".$retNum;
      }else{
        echo "success:".$retNum;
      }
    }else{
      echo "fail1";
    }
  }else{
    echo "fail2";
  }
}

Based setnx exclusive lock program

redis did not like the mysql exclusive lock, but can be achieved through an exclusive lock function in some way, it is similar to using php file locks for exclusive locks the same.

setnx realizes the functions of two instructions set exists and, if the given key already exists, it does nothing setnx return 0; if the key does not exist, a similar set of operations is performed, a return. We set a timeout timeout, try setnx operation at regular intervals, if success is set to obtain the corresponding lock, num execution of decr operation, the operation is completed the appropriate key, release the lock simulate deletion.

public function run(){
  do {
    $res = $this->redis->setnx("numKey",1);
    $this->timeout -= 100;
    usleep(100);
  }while($res == 0 && $this->timeout>0);
  if($res == 0){
    echo 'fail1';
  }else{
    $num = $this->redis->get('num');
    if($num > 0) {
      $this->redis->decr('num');
      usleep(100);
      $res = $this->redis->lPush('result',$num);
      if($res == false){
        echo "fail2";
      }else{
        echo "success:".$num;
      }
    }else{
      echo "fail3";
    }
    $this->redis->del("numKey");
  }
}

 

Guess you like

Origin www.cnblogs.com/freespider/p/11324048.html