Автор оригинала: David Wong.
php
/**
* Class redisConcurrent
*/
class RedisConcurrent
{
/** lock key
* @var string
*/
public $_lockKey = 'redis_lock';
/** Redis Class
* @var Redis
*/
private $_redis ;
/** ip
* @var mixed|string
*/
private $ip ='127.0.0.1' ;
/** port
* @var string
*/
private $port = '6379' ;
/** init redis connect
* redisConcurrent constructor.
* @param array $config
*/
public function __construct( $config = [] )
{
if(!empty($config)) {
if(isset($config['ip'])) {
$this->ip = $config['ip'];
}
if(isset($config['port'])){
$this->ip = $config['port'];
}
}
/**
* Redis connection information can be integrated either natively or with other frameworks
*/
$this->_redis = new Redis();
$this->_redis->connect($this->ip,$this->port);
}
* * lock
*@ Param int $intTimeout default expiration time (to avoid deadlocks)
* @return bool
*/
private function lock($intTimeout = 8) {
# The new set has integrated most of the integration operations
$strRet = $this->_redis->set($this->_lockKey, time().rand(10000,99999).rand(1000,9999).rand(100,999), 'ex', $intTimeout, 'nx');
if($strRet) {
return true;
}else{
return false;
}
}
* * unlock
* @throws \Exception
*/
private function unlock()
{
$strRet = $this->_redis->del($this->_lockKey);
if($strRet) {
return true;
}else{
if($this->_redis->get($this->_lockKey)) {
return false ;
}else{
return false ;
}
}
}
/**
* Business-related keys can be inventory, number of items, etc.
*/
const ORDER_KEY = 'order_num';
/**
* User-related keys
*/
const USER_KEY = 'user_num';
/ ** Redis orders
*@ Single Number under Param int $num
*@ Param string $userId user ID
*
* Field number is to facilitate anomaly processing and data search.
*@ Param string $bout market times=> order_num:1, order_num:2
* @return bool
* @throws Exception
*/
public function order( string $userId ,string $bout = '1' ,int $num = 1)
{
$orderKey = self::ORDER_KEY.':'.$bout ;
$userKey = self::USER_KEY.':'.$bout ;
// This method does not possess atomicity and concurrent processing is not able to make conditional judgment.
//$len = $this->_redis->llen();
# Actually, it's n + 1 trigger completion. Here we only do Redis auto-reduction.
$check = $this->_redis->lpop($orderKey);
if(!$check){
# The current order_num is 0!
// Automated replenishment is 100, $bout has certain processing rules, can not be spread randomly
self::autoBuild(100,$bout);
return false ;
}
// Special treatment to avoid n + 1 case
$len = $this->_redis->llen($orderKey) ;
if($len == 0) {
// Automated replenishment is 100, $bout has certain processing rules, can not be spread randomly
self::autoBuild(100,$bout);
return false ;
}
// Adding user data
$result = $this->_redis->lpush($userKey,$userId);
if($result){
return true ;
}else{
return false ;
}
}
/ ** Failure Management
*# Increase current inventory
*# Reducing User Inventory
* @param string $num
* @param string $userId
* @param $bout
* @return bool
* @throws Exception
*/
public function _out(string $num,string $userId,$bout)
{
# At the time of concurrent participation, there were 5 total inventory requests, 10 total requests, 5 successful requests, 1 refund and 1 actual inventory.
# Failure handling and _buildOrder plus the same lock to avoid the accumulation of the last inventory when updating the next inventory
# Only one out and _buildOrder can be executed at the same time, otherwise the lock will report errors and avoid unnecessary deadlocks.
self::lock();
// Reducing User Inventory
$user = $this->_redis->lpop(self::USER_KEY.':'.$bout);
if(!$user) {
return false ;
}
// Increase commodity stocks
$all = $this->_redis->lpush(self::ORDER_KEY.':'.$bout,$userId);
if(!$all) {
// TODO:: Here we need to do fault-tolerant processing, that is, record when the increase of commodity stock fails.
return false ;
}
self::unlock();
}
/ ** Automatic Construction
* @param int $num
* @param $bout
* @throws Exception
*/
private function autoBuild( int $num ,$bout)
{
$a = $this->_redis->get(self::ORDER_KEY.':'.$bout);
if(!$a) {
// Inventory has been closed
$this->_buildOrder(self::ORDER_KEY.':'.$bout,$num);
}
}
/ ** Item Inventory Rules
* @param $orderKey
* @param $num
* @return string
* @throws Exception
*/
private function _buildOrder($orderKey,$num)
{
// lock in
self::lock();
$ckNum ='0'; Redis operation returns to string type
# The total is of the same type as $ckNum or there may be misjudgments
if($num < 0) {
Throw new Exception ('wrong quantity of goods!');
}
$beforeNum = 0 ;
// Last Inventory Judgment ()
if($beforeNum > 0) {
Throw new Exception ('Goods are not sold out!');
}
// Current Inventory Judgment
$length = $this->_redis->llen($orderKey);
if($length > 0) {
Throw new Exception ('Commodities already exist!');
}
// Generate current inventory
while ($ckNum < $num) {
if($ckNum == $num) {
break ;
}else if($ckNum > $num){
break ;
}else{
$ckNum = $this->_redis->lpush($orderKey,1) ;
if($ckNum >=$num) {
break ;
}
}
}
// Recycle success redis are not necessarily successful in concurrency
/*for ($i=1;$i<=$num ;$i++) {
$ckNum = $this->_redis->lpush(self::$_allCoin.self::getNum().':'.$coin,1);
if($ckNum >= $num) {
break ;
}
}*/
// unlock
self::unlock();
return $ckNum ;
}
}