Рубрики
Uncategorized

PHP + параллельный заказ Redis (полный код)

Автор оригинала: 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 ;
    }
}