基于Redis的资源锁

我们在交易系统中通常会用到锁定产品数量等功能。这里分享的即是其中的一种解决方案,使用Redis的script实现的分布式资源锁。代码如下:


//DLock.php
<?php

namespace libraries;
use enums\CachePrefixEnums;

/**
 *
 * @author fzq
 * @comment 以Redis为基础实现的分布式资源锁 需要script的支持
 * @date 2016-11-25
 */
class DLock
{
	/**
	 * 强制初始化(强制覆盖原有值,自行处理善后)
	 */
	const INIT_FORCE = 1;
	
	/**
	 * 非强制初始化 (有则返回剩余资源量,无则直接初始化)
	 */
	const INIT_FORCE_NONE = 2;
	
	/**
	 * 默认资源数量
	 */
	const DEFAULT_INIT_RES_SIZE = 20;
	
	/**
	 * 默认的锁定时间
	 */
	const DEFAULT_LOCK_SECONDS = 100;
	
	const DEFAULT_APPENDIX = '_lockers';
	
	private static $_instance = null;
	
	private $_redis = null;
	private $_di = null;
	
	/**
	 * 强制初始化
	 */
	private $INIT_FORCE = 1;
	
	/**
	 * 不强制初始化
	 */
	private $INIT_FORCE_NONE = 2;
	
	/**
	 * 返回成功
	 */
	private $ERR_SUCCESS = 1;
	
	/**
	 * 返回成功
	 */
	const ERR_SUCCESS = 1;

	
	/**
	 * 参数错误
	 */
	private $ERR_PARAMETERS = -1;
	
	/**
	 * 参数错误
	 */
	const ERR_PARAMETERS = -1;
	
	/**
	 * 未初始化
	 */
	private $ERR_NONE_INIT = -2;
	/**
	 * 未初始化
	 */
	const ERR_NONE_INIT = -2;
	
	/**
	 * 初始化参数错误
	 */
	private $ERR_INIT_PARAMETERS = -3;
	/**
	 * 初始化参数错误
	 */
	const ERR_INIT_PARAMETERS = -3;
	
	/**
	 * 所需资源大于总资源量
	 */
	private $ERR_EXCEED = -4;
	
	/**
	 * 所需资源大于总资源量
	 */
	const ERR_EXCEED = -4;
	
	/**
	 * 资源不够加锁失败
	 */
	private $ERR_INSUFFICIENT = -5;
	/**
	 * 资源不够加锁失败
	 */
	const ERR_INSUFFICIENT = -5;
	
	/**
	 * 无申请者
	 */
	private $ERR_NO_APPLICANTS = -6;
	/**
	 * 无申请者
	 */
	const ERR_NO_APPLICANTS = -6;
	
	/**
	 * 消费失败 可能是已失去资源锁或是其他错误
	 */
	private $ERR_CONSUME = -7;
	/**
	 * 消费失败 可能是已失去资源锁或是其他错误
	 */
	const ERR_CONSUME = -7; 
	
	/**
	 * 资源已耗尽或是未初始化
	 */
	private $ERR_NO_RESOURCE_LEFT = -8;
	/**
	 * 资源已耗尽或是未初始化
	 */
	const ERR_NO_RESOURCE_LEFT = -8;
	
	/**
	 * 剩余资源量
	 */
	private $LEFT_APPENDIX = '_left';

	
	public function __construct( $redis ) 
	{
		$this->_redis = $redis;
	}
	
	public static function getInstance( $redis )
	{
		if( !self::$_instance )
		{
			self::$_instance = new DLock( $redis );
		}
		
		return self::$_instance;
	}
	
	/**
	 * status: done
	 * 初始化锁
	 * @param string $strLockName 资源锁名
	 * @param int $iResID 资源ID
	 * @param int $iBase 初始加锁时的资源
	 * 
	 * return false:错误 int:未消费的资源数
	 */
	public function initLock( string $strLockName, int $iResID, int $iBase = self::DEFAULT_INIT_RES_SIZE, $iForce = self::INIT_FORCE_NONE )
	{
		if( $iResID <= 0 || 
			$iBase <= 0 || 
			!$strLockName || 
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return self::ERR_PARAMETERS;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
// 		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;//用于获取某个资源所有的持锁者ID集
		
		$strScript = <<< EOS
	
			local iCnt = redis.call( 'hGet', '$strLock', '$iResID$this->LEFT_APPENDIX' );
			
			if( $iForce == $this->INIT_FORCE_NONE ) then
				if iCnt then
					return iCnt;
				end
			end
			
			redis.call( 'hSet', '$strLock', $iResID, $iBase );
			redis.call( 'hSet', '$strLock', '$iResID$this->LEFT_APPENDIX', $iBase );
			return true;
		
EOS;
		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * @comment 取剩余的资源数量
	 * 剩余的资源数量 = 总资源量 - 消费的资源量
	 * @param string $strLockName
	 * @param int $iResID
	 * 
	 * return false for error int for resource left
	 */
	public function getResLeftCnt( string $strLockName, int $iResID )
	{
		if( $iResID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return self::ERR_PARAMETERS;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		
		$strScript = <<<EOS
		local strLeft = redis.call( 'hGet', '$strLock', '$iResID$this->LEFT_APPENDIX' );
		if strLeft then
			return strLeft;
		end	
		return false;
EOS;
		
		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * 取初始资源总量
	 * @param $strLockName
	 * @param $iResID
	 * 
	 * return false for error others for success
	 */
	public function getResInitCnt( string $strLockName, int $iResID )
	{
		if( $iResID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return self::ERR_PARAMETERS;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		
		$strScript = <<<EOS
		local strCnt = redis.call( 'hGet', '$strLock', $iResID );
		if strCnt then
			return strCnt;
		end
		return $this->ERR_NONE_INIT;
EOS;
		
		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * 取资源使用情况
	 * 返回资源的初始值, 未消费资源量, 资源名, 资源ID
	 * 此接口主要用于前端显示规格之类的销售情况
	 * 
	 * return array for success false for error
	 */
	public function getResStatus( string $strLockName, int $iResID )
	{
		if( $iResID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return self::ERR_PARAMETERS;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		
		$strScript = <<<EOS
		return redis.call( 'hMGet', '$strLock', $iResID, '$iResID$this->LEFT_APPENDIX' );
EOS;
	
		$arrRet = $this->_redis->eval( $strScript );
		if( is_array( $arrRet ) && $arrRet[ 0 ] )
		{
			return array( 'name' => $strLockName, 'id' => $iResID, 'max' => $arrRet[0], 'left' => $arrRet[1] );
		}
		
		return self::ERR_NONE_INIT;
	}
	
	/**
	 * @param string $strLockName 资源名
	 * @param int $iResID 资源ID
	 * @param int $iApplicantID 资源的申请者ID
	 * @param int $iRequestNum 需要的资源数量
	 * @param int $holdSeconds
	 * @return boolean
	 */
	public function lockRes( string $strLockName, int $iResID, int $iApplicantID, int $iRequestNum, int $iHoldSeconds = self::DEFAULT_LOCK_SECONDS )
	{
		/*
		 * 算法:此资源锁使用共使用了两类数据即hash与string共三组
	 	 * h_s_lock_{$strLockName}:锁 所有的资源锁的最大资源数存放于存hash中采用 如spec numbers规格数 其中的每一组 $iResID:资源数 即是该类的一组资源
	 	 * h_s_lock_res_{$strLockName}_{$iResID}:这里存放的是持锁请求者的ID,其中的每一项即为 $iApplicantID:$
		 */
		
		if( $iResID <= 0 ||
			$iApplicantID <= 0 ||
			$iRequestNum <= 0 ||
			$iHoldSeconds <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strLockHash = CachePrefixEnums::LOCK_RES_PREFIX . $strLockName;//资源锁集
		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;//用于获取某个资源所有的持锁者ID集
		$strApplicantKeyPrefix = CachePrefixEnums::LOCK_RES_ITEM_PREFIX . $strLockName . '_' . $iResID . '_';//资源持有者ID用于获取string
		
		$strScript = <<< EOS
		
		local strSum = redis.call( 'hGet', '$strLockHash', '$iResID$this->LEFT_APPENDIX' );
		local iSum = 0;
		
		if( strSum  ) then
			iSum = tonumber( strSum );
		else
			return $this->ERR_NONE_INIT;
		end
		
		local arrKeys = redis.call( 'lrange', '$strListApplicantKeys', 0, -1 );
		
		if( arrKeys and #arrKeys == 0 ) then
		
			if( iSum <= 0 ) then
				return $this->ERR_INSUFFICIENT; 
			elseif( $iRequestNum > iSum ) then
				return $this->ERR_EXCEED;	
			end
			
			redis.call( 'rpush', '$strListApplicantKeys', $iApplicantID );
			local strLockItem = '$strApplicantKeyPrefix' .. $iApplicantID;
			redis.call( 'set', strLockItem, $iRequestNum, 'EX', $iHoldSeconds );
			
			return $this->ERR_SUCCESS;
		else
			local arrStrKeys = {};
			local iKeySize = table.getn( arrKeys );
			local bFoundApplicant = false;
			local iLockRes = 0;
			
			for i = 1, iKeySize do
				if( tonumber( arrKeys[ i ] ) == $iApplicantID ) then
					local strLockRes = redis.call( 'get', '$strApplicantKeyPrefix' .. $iApplicantID );
					if( strLockRes and ( tonumber( strLockRes ) <= $iRequestNum )) then
						local strLockItem = '$strApplicantKeyPrefix' .. $iApplicantID;
						redis.call( 'set', strLockItem, $iRequestNum, 'EX', $iHoldSeconds );
						return $this->ERR_SUCCESS;
					end
					
					if strLockRes then
						iLockRes = tonumber( strLockRes );
					end
					bFoundApplicant = true;
				end
				
				table.insert( arrStrKeys, '$strApplicantKeyPrefix' .. arrKeys[ i ] );
			end 

			local arrRes = redis.call( 'mget', unpack( arrStrKeys ) );
			local iLocked = 0;
			local iArrRes = table.getn( arrRes );
			for i = 1, iArrRes do
				if( arrRes[ i ] ) then
					iLocked = iLocked + arrRes[ i ];
				end
			end
					
			if( iLocked - iLockRes + $iRequestNum <= iSum ) then
				if( not bFoundApplicant ) then
					redis.call( 'rpush', '$strListApplicantKeys', $iApplicantID );
				end
				redis.call( 'set', '$strApplicantKeyPrefix' .. $iApplicantID, $iRequestNum, 'EX', $iHoldSeconds );
				return $this->ERR_SUCCESS;
			end
						
			return $this->ERR_INSUFFICIENT; 
		end
	
EOS;

		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * status: to be done
	 * @param string $strLockName
	 * @param int $iID
	 * 
	 * return true for success others for error
	 */
	public function unlockRes( string $strLockName, int $iResID, int $iApplicantID )
	{//删除list中的key清除string中相关的key
		
		if( $iResID <= 0 ||
			$iApplicantID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;
		$strApplicantKey = CachePrefixEnums::LOCK_RES_ITEM_PREFIX . $strLockName . '_' . $iResID . '_' . $iApplicantID;
		
		$strScript = <<<EOS
		redis.call( 'lRem', '$strListApplicantKeys', 0, $iApplicantID );
		redis.call( 'del', '$strApplicantKey' );
		return true;
EOS;
		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * 删除资源锁
	 * @param string $strLockName
	 * @param int $iResID
	 * @return number
	 */
	public function delLock( string $strLockName, int $iResID )
	{
		if( $iResID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;
		$strApplicantKeyPrefix = CachePrefixEnums::LOCK_RES_ITEM_PREFIX . $strLockName . '_' . $iResID . '_';
		$strConsumeLog = CachePrefixEnums::LOCK_RES_LOCK_CONSUME_PREFIX . $iResID;//消费记录
		
		$strScript = <<<EOS
		local arrApps = redis.call( 'lrange', '$strListApplicantKeys', 0, -1 );
		local strKey = '';
		if( arrApps and #arrApps > 0 ) then
			local iSize = #arrApps;
			for i = 1, iSize do
				strKey = '$strApplicantKeyPrefix' .. arrApps[ i ];
				redis.call( 'del', strKey );
			end
			redis.call( 'del', '$strListApplicantKeys' );
		end
		redis.call( 'hDel', '$strLock', $iResID );
		redis.call( 'hDel', '$strLock', '$iResID$this->LEFT_APPENDIX' );
		redis.call( 'del', '$strConsumeLog' );
		return true;
EOS;
		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * 消费资源 这里不会出现重复消费的情况
	 * 
	 * @param string $strLockName
	 * @param int $iResID
	 * @param int $iApplicantID
	 * 
	 * return true for success others for error
	 */
	public function consume( string $strLockName, int $iResID, int $iApplicantID )
	{
		if( $iResID <= 0 ||
			$iApplicantID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;
		$strApplicantKey = CachePrefixEnums::LOCK_RES_ITEM_PREFIX . $strLockName . '_' . $iResID . '_' . $iApplicantID;
		$strConsumeLog = CachePrefixEnums::LOCK_RES_LOCK_CONSUME_PREFIX . $iResID;
		
		$strScript = <<<EOS
		
		local strAppNum = redis.call( 'get', '$strApplicantKey' );
		if strAppNum then
			local iAppNum = tonumber( strAppNum );
			redis.call( 'hIncrBy', '$strLock', '$iResID$this->LEFT_APPENDIX', -1 * iAppNum );
			redis.call( 'lRem', '$strListApplicantKeys', 0, $iApplicantID );
			redis.call( 'del', '$strApplicantKey' );
			redis.call( 'hSet', '$strConsumeLog', $iApplicantID, iAppNum );		
			return iAppNum;
		end
		
		return $this->ERR_CONSUME;
EOS;
		
		return $this->_redis->eval( $strScript );
	}
	
	/**
	 * 取资源申请者及其申请的资源量
	 * 
	 * @param string $strLockName
	 * @param int $iResID
	 * @return int for error object for success
	 */
	public function getApplicants( string $strLockName, int $iResID )
	{
		if( $iResID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;
		$strApplicantKeyPrefix = CachePrefixEnums::LOCK_RES_ITEM_PREFIX . $strLockName . '_' . $iResID . '_';
		
		$strScript = <<< EOS
		
			local arrKeys = redis.call( 'lrange', '$strListApplicantKeys', 0, -1 );
			if( not arrKeys or #arrKeys == 0 ) then
				return $this->ERR_NO_APPLICANTS;
			end

			local arrStrKeys = {};
			local iKeySize = table.getn( arrKeys );
						
			for i = 1, iKeySize do
				table.insert( arrStrKeys, '$strApplicantKeyPrefix' .. arrKeys[ i ] );
			end 
			local arrRes = redis.call( 'mget', unpack( arrStrKeys ) );	
			local arrRets = {};
			for i = iKeySize, 1, -1 do
				if( arrRes[i] ) then
					arrRets[ arrKeys[i] ] = arrRes[i];
				else
					redis.call( 'lRem', '$strListApplicantKeys', 0, arrKeys[i] );
					redis.call( 'del', '$strApplicantKeyPrefix .. arrKeys[i]' );
				end
			end
							
			if( next( arrRets )  ) then
				return cjson.encode( arrRets );
			else
				return $this->ERR_NO_APPLICANTS;
			end
EOS;

		
		$ret = $this->_redis->eval( $strScript );
		if( $ret && is_string( $ret ))
		{
			return json_decode( $ret );
		}
		
		return $ret;
	}
	
	/**
	 * 剩余的未加锁的资源与剩余的资源总量
	 * @param $strLockName
	 * @param $iResID
	 * 
	 * array for success int for error e.g. 
	 * object( public 'locked' => 11, public 'left' => '17' )
	 */
	public function getNoneLockedRes( string $strLockName, int $iResID )
	{
		if( $iResID <= 0 ||
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		$strListApplicantKeys = CachePrefixEnums::LOCK_RES_LIST_KEYS_PREFIX . $strLockName . '_' . $iResID;
		$strApplicantKeyPrefix = CachePrefixEnums::LOCK_RES_ITEM_PREFIX . $strLockName . '_' . $iResID . '_';
		
		$strScript = <<< EOS
			local strLeft = redis.call( 'hGet', '$strLock', '$iResID$this->LEFT_APPENDIX' );
			if not strLeft then
				return $this->ERR_NONE_INIT; 
			end
			
			local arrRets = {};
			
			local arrKeys = redis.call( 'lrange', '$strListApplicantKeys', 0, -1 );
			if( not arrKeys or #arrKeys == 0 ) then
				
				arrRets['locked'] = 0;
				arrRets['left'] = strLeft;
				
				return cjson.encode( arrRets );
			end

			local arrStrKeys = {};
			local iKeySize = table.getn( arrKeys );
						
			for i = 1, iKeySize do
				table.insert( arrStrKeys, '$strApplicantKeyPrefix' .. arrKeys[ i ] );
			end 
			
			local arrRes = redis.call( 'mget', unpack( arrStrKeys ) );	
			local iSum = 0;
			for i = iKeySize, 1, -1 do
				if( arrRes[i] ) then
					iSum = iSum + tonumber( arrRes[i] );
				else
					redis.call( 'lRem', '$strListApplicantKeys', 0, arrKeys[i] );
					redis.call( 'del', '$strApplicantKeyPrefix .. arrKeys[i]' );
				end
			end
			
			arrRets[ 'locked' ] = iSum;
			arrRets[ 'left' ] = tonumber( strLeft ); 
							
			return cjson.encode( arrRets );
EOS;

		
		$ret = $this->_redis->eval( $strScript );
		if( $ret && is_string( $ret ))
		{
			return json_decode( $ret );
		}
		
		return $ret;
	}
	
	/**
	 * 退款后需要回滚Res
	 * @param string $strLockName
	 * @param int $iResID
	 * @param int $iApplicantID
	 * @param int $iCnt 0:退回所有已消费的, >0 退回指定数目已消费的
	 * 
	 * return -1:参数错误 false:错误 true for success 
	 */
	public function incrRes( string $strLockName, int $iResID, int $iApplicantID, int $iCnt = 0 )
	{
		if( $iResID <= 0 ||
			$iCnt < 0 ||
			$iApplicantID <= 0 || 
			!$strLockName ||
			( $strLockName = trim( $strLockName ) ) == '' )
		{
			return $this->ERR_PARAMETERS;//ERR_PARAMETERS = -1;
		}
		
		$strConsumeLog = CachePrefixEnums::LOCK_RES_LOCK_CONSUME_PREFIX . $iResID;
		$strLock = CachePrefixEnums::LOCK_RES_PREFIX .  $strLockName;
		
		/*
		 * 为了性能做了一些妥协: script中本应该使用两个变量转而使用了一个变量iConsumedNum来代替两个变量的功能特此说明
		 */
		$strScript = <<<EOS
		local strConsumedNum = redis.call( 'hGet', '$strConsumeLog', $iApplicantID );
		
		if strConsumedNum then
			local iConsumedNum = tonumber( strConsumedNum );
			if $iCnt > iConsumedNum then
				return false;
			end
					
			if 0 ~= $iCnt then
				iConsumedNum = $iCnt;
			end
			
			redis.call( 'hIncrBy', '$strConsumeLog', $iApplicantID, -1 * iConsumedNum );
			redis.call( 'hIncrBy', '$strLock', '$iResID$this->LEFT_APPENDIX', iConsumedNum );

			return true;
		end
		
		return false;
EOS;
		
		$iRet = $this->_redis->eval( $strScript );
		
		if( $iRet )
		{
			return true;
		}
		
		return false;
	}

}

//CachePrefixEnums.php
<?php
namespace enums;

/**
 * 
 * @author fzq
 * 
 */
class CachePrefixEnums
{
	
	//********************************************************************************
	//DLock.php
	/**
	 * 某个资源持有者list前缀
	 */
	const LOCK_RES_LIST_KEYS_PREFIX = 'l_s_res_lock_keys_';
	
	/**
	 * 持锁者前缀
	 */
	const LOCK_RES_ITEM_PREFIX = 'str_s_res_lock_item_';
	
	/**
	 * 资源锁 资源数量hash前缀 主要用来存放资源的个数及剩余数量等
	 */
	const LOCK_RES_PREFIX = 'h_s_res_lock_';
	
	/**
	 * 消费记录前缀
	 */
	const LOCK_RES_LOCK_CONSUME_PREFIX = 'h_s_res_lock_consume_log_';
	
	
	
	//********************************************************************************
	//RedLock
	/**
	 * 扩展锁
	 */
	const LOCK_EX_PREFIX = 'str_s_redlock_exlock_';
	
	/**
	 * 普通锁
	 */
	const LOCK_PREFIX = 'str_s_redlock_lock_';
	
}

贴上使用方法:
DLock::getInstance( $this->di );
		
		$strLockName = 'fortest_bruce' . time();
		$iResID = 1;
		$iBase = 11;
		
		$this->_ut->setTitle( 'DLock测试' );
		//test for initlock
		$this->_ut->run( ( $iRet = $dLock->initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) ) >= 1, true, 
				"initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) 返回值: $iRet", '测试initLock' );
		
		$this->_ut->run( ( $iRet = $dLock->initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) ) >= 1, true,
				"initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) 返回值: $iRet", '测试initLock' );
		
		$iBase = 22;
		$this->_ut->run( ( $iRet = $dLock->initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) ) >= 1, true,
				"initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) 返回值: $iRet", '测试initLock' );
		
		$this->_ut->run( ( $iRet = $dLock->initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE ) ) >= 1, true,
				"initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE ) 返回值: $iRet", '测试initLock' );
		
		$this->_ut->run( ( $iRet = $dLock->initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) ) >= 1, true,
				"initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) 返回值: $iRet", '测试initLock' );
		
		$this->_ut->run( ( $iRet = $dLock->initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE ) ) >= 1, true,
				"initLock( $strLockName, $iResID, $iBase, DLock::INIT_FORCE_NONE ) 返回值: $iRet", '测试initLock' );
		
		//*********************************************************************************************************
		//test for getApplicants
		$this->_ut->run( ( DLock::ERR_NO_APPLICANTS == $dLock->getApplicants( $strLockName, $iResID )), true, 
				"getApplicants( $strLockName, $iResID )", '测试getApplicants' );
		
		
		//test for getNoneLockedRes
		$this->_ut->run( (($objRet = $dLock->getNoneLockedRes($strLockName, $iResID)) && is_object( $objRet ) && 
				( $objRet->locked == 0 ) && ( $objRet->left == $iBase )), 
				true, 
				"getNoneLockedRes($strLockName, $iResID)" . var_export( $objRet, true ),
				'测试getNoneLockedRes' );
		
		//test for getResInitCnt
		$this->_ut->run( ( $iRet = $dLock->getResInitCnt($strLockName, $iResID)) == $iBase, true, 
						"getResInitCnt($strLockName, $iResID) 返回值: $iRet", '测试getResInitCnt');
		
		//test for getResStatus
		$this->_ut->run( ( $objRet = $dLock->getResStatus($strLockName, $iResID)) && $objRet['name'] == $strLockName &&
				$objRet['max'] == $iBase && $objRet['left'] == $iBase, 
				true, "getResStatus($strLockName, $iResID) 返回值:" . var_export( $objRet, true ), '测试getResStatus');
		
		//************************************************************************************************************************
		
		$iApplicantID = 2;
		$iRequireNum = 3;
		//test for lockres
		$this->_ut->run( ($iRet = $dLock->lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum )), 
				1, 
				"lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum ) 返回值: $iRet", 
				'测试lockRes' );
		
		$this->_ut->run( ($iRet = $dLock->lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum )),
				1,
				"lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum ) 返回值:" . $iRet,
				'测试lockRes' );
		
		//test for consume
		$this->_ut->run( ($iRet =  $dLock->consume( $strLockName, $iResID, $iApplicantID )), $iRequireNum, 
				"consume($strLockName, $iResID, $iApplicantID ) 返回值: $iRet", 
				'测试consume' 
				);
		
		$this->_ut->run( ( $objRet = $dLock->getResStatus($strLockName, $iResID)) && $objRet['name'] == $strLockName &&
				$objRet['max'] == $iBase && $objRet['left'] == $iBase - $iRet,
				true, "getResStatus($strLockName, $iResID) 返回值:" . var_export( $objRet, true ), '测试getResStatus');
		
		$this->_ut->run( ($iRet =  $dLock->consume( $strLockName, $iResID, $iApplicantID )), DLock::ERR_CONSUME,
				"consume($strLockName, $iResID, $iApplicantID ) 返回值: $iRet",
				'测试consume'
				);
		

			
		$this->_ut->run( $iRet = $dLock->incrRes( $strLockName, $iResID, $iApplicantID, 1 ),
				1,
				"incrRes( $strLockName, $iResID, $iApplicantID, 1 ) 返回值: " . $iRet,
				'测试incrRes( $strLockName, $iResID, $iApplicantID, 1 )' );
		
		$this->_ut->run( $iRet = $dLock->incrRes( $strLockName, $iResID, $iApplicantID, 3 ),
				false,
				"incrRes( $strLockName, $iResID, $iApplicantID, 1 ) 返回值: " . $iRet,
				'测试incrRes( $strLockName, $iResID, $iApplicantID, 1 )' );
		
		$this->_ut->run( $iRet = $dLock->incrRes( $strLockName, $iResID, $iApplicantID, 1 ),
				true,
				"incrRes( $strLockName, $iResID, $iApplicantID, 1 ) 返回值: " . $iRet,
				'测试incrRes( $strLockName, $iResID, $iApplicantID, 1 )' );
		
		$this->_ut->run( ($iRet = $dLock->lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum )),
				1,
				"lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum ) 返回值: $iRet",
				'测试lockRes' );
		
		$this->_ut->run( ($iRet = $dLock->lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum )),
				1,
				"lockRes($strLockName, $iResID, $iApplicantID, $iRequireNum ) 返回值: $iRet",
				'测试lockRes' );
		
		
		$this->_ut->run( ($objRet = $dLock->getApplicants($strLockName, $iResID)) && is_object( $objRet ), true,
				"consume($strLockName, $iResID, $iApplicantID ) 返回值: $iRet",
				'测试consume'
		);
		
		//test for getNoneLockedRes
		$this->_ut->run( (($objRet = $dLock->getNoneLockedRes($strLockName, $iResID)) && is_object( $objRet ) &&
				( $objRet->locked == $iRequireNum ) && ( $objRet->left == 21 )),
				true,
				"getNoneLockedRes($strLockName, $iResID)" . var_export( $objRet, true ),
				'测试getNoneLockedRes' );
		
		//test for getResInitCnt
		$this->_ut->run( ( $iRet = $dLock->getResInitCnt($strLockName, $iResID)) == $iBase, true,
				"getResInitCnt($strLockName, $iResID) 返回值: $iRet", '测试getResInitCnt');
		
		//test for getResStatus
		$this->_ut->run( ( $objRet = $dLock->getResStatus($strLockName, $iResID)) && $objRet['name'] == $strLockName &&
						$objRet['max'] == $iBase && $objRet['left'] == 21,
						true, "getResStatus($strLockName, $iResID) 返回值:" . var_export( $objRet, true ), '测试getResStatus');
				
				
		$this->_ut->run( ($iRet =  $dLock->consume( $strLockName, $iResID, $iApplicantID )), $iRequireNum,
				"consume($strLockName, $iResID, $iApplicantID ) 返回值: $iRet",
				'测试consume'
		);
		
		$this->_ut->run( ($iRet =  $dLock->delLock($strLockName, $iResID)), DLock::ERR_SUCCESS,
				"delLock($strLockName, $iResID) 返回值: $iRet",
				'测试delLock'
		);
		
		echo $this->_ut->report();




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值