Several Solutions of PHP Concurrency Scenario

  • 2021-12-11 17:34:55
  • OfStack

In concurrent scenarios such as spike and snap-up, oversold may occur. There is no native concurrency solution in PHP language, so it is necessary to realize concurrency control by other means.

Common solutions listed are:

Using the queue, an extra process to process the queue, concurrent requests are put into the queue, by the extra process serial processing, the concurrency problem does not exist, but the extra process support and processing delay is serious, this paper will not discuss this method first. Using the transaction characteristics of database to do atomic update, this method needs to rely on the transaction characteristics of database. With the help of file exclusive lock, when processing an order request, lock a file with flock, and only those who successfully get the lock can process the order.

1. Leveraging Redis transaction features

redis transactions are atomic operations, which can ensure that data is not modified by other concurrent processes during order processing.

Sample code:


<?php
$http = new swoole_http_server("0.0.0.0", 9509);  //  Eavesdropping  9509

$http->set(array(
  'reactor_num' => 2, //reactor thread num
  'worker_num' => 4  //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
  $uniqid = uniqid('uid-', TRUE);  //  Analog only 1 Users ID
  $redis = new Redis();
  $redis->connect('127.0.0.1', 6379);  //  Connect  redis

  $redis->watch('rest_count'); //  Monitoring  rest_count  Is it changed by another process 

  $rest_count = intval($redis->get("rest_count")); //  Analog only 1 Order ID
  if($rest_count > 0){
    $value = "{$rest_count}-{$uniqid}"; //  Indicates the current order, which was grabbed by the current user 

    // do something ...  It mainly simulates that the user may have to do it after grabbing the order 1 Some intensive operations 
    $rand = rand(100, 1000000);
    $sum=0;
    for ($i=0;$i<$rand;$i++){ $sum+=$i; }

   // redis  Affairs 
    $redis->multi();
    $redis->lPush('uniqids', $value);
    $redis->decr('rest_count');
    $replies = $redis->exec(); //  Execute the above  redis  Affairs 

   //  If  rest_count  Is changed by another concurrent process, the above transaction will be rolled back 
    if(!$replies){
      echo " Order  {$value}  Rollback ".PHP_EOL;
    }
  }
  $redis->unwatch();
});

$http->start();

Test with ab


$ ab -t 20 -c 10 http://192.168.1.104:9509/

2. Use file exclusive lock (blocking mode)

In blocking mode, if a process acquires a file exclusive lock while another process is occupying the lock, the process will suspend and wait for other processes to release the lock, and then execute after acquiring the lock by itself.

Sample code:


<?php
$http = new swoole_http_server("0.0.0.0", 9510);

$http->set(array(
  'reactor_num' => 2, //reactor thread num
  'worker_num' => 4  //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {

  $uniqid = uniqid('uid-', TRUE);
  $redis = new Redis();
  $redis->connect('127.0.0.1', 6379);

  $fp = fopen("lock.txt", "w+");

  //  Obstruction ( Wait ) Mode,   To get an exclusive lock (written program) 
  if(flock($fp,LOCK_EX))  // Lock the current pointer 
  {
   //  After successfully obtaining the lock, handle the order with confidence 
    $rest_count = intval($redis->get("rest_count"));
    $value = "{$rest_count}-{$uniqid}";
    if($rest_count > 0){
      // do something ...
      $rand = rand(100, 1000000);
      $sum=0;
      for ($i=0;$i<$rand;$i++){ $sum+=$i; }

      $redis->lPush('uniqids', $value);
      $redis->decr('rest_count');
    }

   //  Release the lock after the order processing is completed 
    flock($fp,LOCK_UN);
  }
  fclose($fp);

});

$http->start();

Test with ab


$ ab -t 20 -c 10 http://192.168.1.104:9510/

3. Use file exclusive lock (non-blocking mode)

In non-blocking mode, if a process acquires an exclusive lock on a file while another process is holding the lock, the process immediately judges that the lock acquisition failed and continues to execute.

Sample code:


<?php
$http = new swoole_http_server("0.0.0.0", 9511);

$http->set(array(
  'reactor_num' => 2, //reactor thread num
  'worker_num' => 4  //worker process num
));

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {

  $uniqid = uniqid('uid-', TRUE);
  $redis = new Redis();
  $redis->connect('127.0.0.1', 6379);

  $fp = fopen("lock.txt", "w+");

  //  Non-blocking mode,   If you don't want to  flock()  If it is blocked when locking, it will be given to  lock  Plus  LOCK_NB
  if(flock($fp,LOCK_EX | LOCK_NB))  // Lock the current pointer 
  {
   //  After successfully obtaining the lock, handle the order with confidence 
    $rest_count = intval($redis->get("rest_count"));
    $value = "{$rest_count}-{$uniqid}";
    if($rest_count > 0){
      // do something ...
      $rand = rand(100, 1000000);
      $sum=0;
      for ($i=0;$i<$rand;$i++){ $sum+=$i; }

      $redis->lPush('uniqids', $value);
      $redis->decr('rest_count');
    }

   //  Release the lock after the order processing is completed 
    flock($fp,LOCK_UN);
  } else {
   //  If the lock acquisition fails, enter here immediately to execute 
    echo "{$uniqid} -  The system is busy, please try again later ".PHP_EOL;
  }
  fclose($fp);

});

$http->start();

Test with ab


$ ab -t 20 -c 10 http://192.168.1.104:9511/

Finally, the test results of three treatment methods are compared

redis transaction mode:


......
Concurrency Level:   10
Time taken for tests:  20.005 seconds
Complete requests:   17537
Failed requests:    0
Total transferred:   2578380 bytes
HTML transferred:    0 bytes
Requests per second:  876.62 [#/sec] (mean)
Time per request:    11.407 [ms] (mean)
Time per request:    1.141 [ms] (mean, across all concurrent requests)
Transfer rate:     125.86 [Kbytes/sec] received
......

File exclusive lock (blocking mode):


......
Concurrency Level:   10
Time taken for tests:  20.003 seconds
Complete requests:   8205
Failed requests:    0
Total transferred:   1206282 bytes
HTML transferred:    0 bytes
Requests per second:  410.19 [#/sec] (mean)
Time per request:    24.379 [ms] (mean)
Time per request:    2.438 [ms] (mean, across all concurrent requests)
Transfer rate:     58.89 [Kbytes/sec] received
......

File exclusive lock (non-blocking mode):


......
Concurrency Level:   10
Time taken for tests:  20.002 seconds
Complete requests:   8616
Failed requests:    0
Total transferred:   1266846 bytes
HTML transferred:    0 bytes
Requests per second:  430.77 [#/sec] (mean)
Time per request:    23.214 [ms] (mean)
Time per request:    2.321 [ms] (mean, across all concurrent requests)
Transfer rate:     61.85 [Kbytes/sec] received
......

Compared with the test results, redis transaction mode is better than file exclusive lock mode, and non-blocking mode is better than blocking mode in file exclusive lock mode.


Related articles: