In the concurrency scenarios such as seckill and rush to buy, oversold may occur. There is no native concurrency solution in PHP language, so we need to use other methods to achieve concurrency control.
Common solutions listed are:
With queue, an extra process is set up to process the queue, and the concurrent requests are put into the queue. If the additional processes are processed serially, the concurrency problem will not exist. However, to support the additional processes and the processing delay is serious, this method will not be discussed in this paper.
This method depends on the transaction characteristics of the database.
With the help of file exclusive lock, when processing the order request, lock a file with flock, and the order can only be processed if the lock is successfully obtained.
1, Taking advantage of Redis Transaction Features
redis transactions are atomic operations, which can ensure that the data in the order processing process is not modified by other concurrent processes.
Example code:
<?php $http = new swoole_http_server("0.0.0.0", 9509); // Monitor 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); // Impersonate unique user ID $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // Connect to redis $redis->watch('rest_count'); // Monitor rest_ Whether the count is changed by other processes $rest_count = intval($redis->get("rest_count")); // Simulate unique order ID if ($rest_count > 0){ $value = "{$rest_count}-{$uniqid}"; // Indicates the current order, which is seized by the current user // do something... Is mainly to simulate some intensive operations that may be carried out after the user grabs the order $rand = rand(100, 1000000); $sum = 0; for ($i = 0; $i < $rand; $i++) {$sum += $i;} // redis transaction $redis->multi(); $redis->lPush('uniqids', $value); $redis->decr('rest_count'); $replies = $redis->exec(); // Execute the above redis transaction // If rest_ The value of count has been changed by other concurrent processes, and the above transactions 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, Using file exclusive lock (blocking mode)
In blocking mode, if other processes are occupying the lock when the process acquires the exclusive lock of the file, the process will suspend and wait for other processes to release the lock, and then execute after obtaining the lock by itself.
Example 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+"); // Blocking (waiting) mode, to get exclusive lock (written program) if (flock($fp,LOCK_EX)) { //Lock current pointer // Handle the order with confidence after obtaining the lock successfully $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, Using file exclusive lock (non blocking mode)
In non blocking mode, if other processes are occupying the lock while acquiring the exclusive lock of the file, the process will immediately judge the failure of acquiring the lock and continue to execute. "
Example 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 float() to block when locking, add lock to the LOCK_NB if(flock($fp,LOCK_EX | LOCK_NB)) //Lock current pointer { // Handle orders with confidence after successful lock acquisition $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 to execute immediately 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 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 ......
The test results show that redis transaction mode is better than file exclusive locking mode, while non blocking mode is better than blocking mode.
For more information, please visit: