基于redis的分布式互斥锁

我们在进行BS开如经常遇到的一类问题即是对记录的竞态访问,最近我们的平台也遇到了类似的问题,查找了一下几个比较有名的开源电商项目发现他们并没有实现记录的并发访问,于是自己动手实现了一个,典型的应用场景如下:一个用户审核记录时另一用户不需再审核;一个用户在处理发货时另一用户不用再处理;

现与各位分享(若想实现安全的锁则只需要设置不规律的$uid即可):


/**
 *
 * @author fzq
 * @comment 以Redis为基础实现的分布式锁 需要script的支持
 * @date 2016-09-02
 */
class RedLock //implements \Phalcon\DI\InjectionAwareInterface
{
	private $redis = null;
// 	private $_di = null;
	const LOCK_EX_PREFIX = 'str_exlock_';
	const LOCK_PREFIX = 'str_lock_';
	
	public function __construct( $redis ) 
	{
		$this->redis = $redis;
	}
	
	/**
	 * 加扩展锁
	 * 
	 * 算法:先查看是否持有锁 如果持有锁则直接返回
	 * 否则加锁
	 * 加锁失败则返回false否则返加真
	 * @param string $strKey: biz key
	 * @param string $irecor: record id
	 * @param string $uid: user id
	 * @param int $ttl time to live
	 * @return boolean|number
	 */
	public function lockEx( $strBiz, $iRecordID, $uid, $ttl = 60 )
	{
		if( !$this->redis )
			return false;
		
		$curTime = TimeUtils::getIntTime();
		$strKeyID = '"' . self::LOCK_EX_PREFIX . $strBiz . '_' . $iRecordID .  '"';
// 		$strUID = '"' . $uid . '"';
		
		if( is_string( $uid ) )
		{
			$uid = '"' . $uid . '"';
		}
		
$script = <<<EOT
local ttl = redis.call( 'ttl', $strKeyID );
if( ttl >= $ttl ) then
	
	local strVal = redis.call( 'get', $strKeyID );
	local objVal = cjson.decode( strVal );
	 
	if objVal['uid'] == $uid then
		return true;
	else
		return false;
	end
			
elseif ttl < 0 then
	
	local val = {};
	val["uid"] = $uid;
	val["localTime"] = $curTime;
	local setRes = redis.call( 'setnx', $strKeyID, cjson.encode( val ) );
	
	if setRes == 1 then
	
	return redis.call( 'expire', $strKeyID, $ttl );
			
	end
	
elseif ttl > 0 and ttl < $ttl then
	
	local strVal = redis.call( 'get', $strKeyID );
	local objVal = cjson.decode( strVal );
	 
	if objVal['uid'] == $uid then
			return redis.call( 'expire', $strKeyID, $ttl );
	else
		return false;
	end
			
else
			
	return false;
		
end
EOT;
		return $this->redis->eval( $script );
	}
	
	/**
	 * 加锁
	 * 
	 * @param string $strBiz
	 * @param int $id
	 * @param int $ttl
	 * @return boolean
	 */
	public function lock( $strBiz, $iRecordID, $ttl = 60 ) 
	{
		if( !$this->redis )
			return false;
		
		$strKeyID = self::LOCK_PREFIX . $strBiz . '_' . $iRecordID;
		
		if( $this->redis->set ( $strKeyID, TimeUtils::getIntTime(), [
				'NX',
				'EX' => $ttl
				] ))
		{
			return true;
		}

		return false;
	}

	/**
	 * 解扩展锁
	 * 
	 * 只能解自己持有的锁
	 * @param string $strBiz
	 * @param int $id
	 * @param int $uid
	 */
	public function unlockEx(  $strBiz, $iRecordID, $uid )
	{
		if( !$this->redis )
			return false;
		
		$strKeyID = '"' . self::LOCK_EX_PREFIX . $strBiz . '_' . $iRecordID .  '"';
		if( is_string( $uid ) )
		{
			$uid = '"' . $uid . '"';
		}
		
		$script = <<<EOT
local lockData = redis.call( 'get', $strKeyID );
		
if lockData == false then

return true;
		
else
		local objData = cjson.decode( lockData );

		if( objData[ 'uid' ] == $uid ) then
				return redis.call( 'del', $strKeyID );
		end

		return false;
end

EOT;
		return $this->redis->eval( $script );
	}
	
	/**
	 * 解锁
	 * @param string $strBiz
	 * @param int $iRecordID
	 */
	public function unlock( $strBiz, $iRecordID ) 
	{
		if( !$this->redis )
			return false;
		
		$strKeyID = self::LOCK_PREFIX . $strBiz . '_' . $iRecordID;
		
		return $this->redis->del ( $strKeyID );
	}
	
	/**
	 * 检测是否持有锁
	 * 
	 * 只对扩展锁有效
	 * @param $strBiz
	 * @param $id
	 * @param $uid
	 */
	public function isHoldLock( $strBiz, $iRecordID, $uid )
	{		
		if( !$this->redis )
			return false;
		
		if( is_string( $uid ) )
		{
			$uid = '"' . $uid . '"';
		}
		
		$strKeyID = '"' . self::LOCK_EX_PREFIX . $strBiz . '_' . $iRecordID .  '"';

		$script = <<<EOT
local lockData = redis.call( 'get', $strKeyID );
		
if lockData == false then

	return false;
		
else
	local objData = cjson.decode( lockData );

	if( objData[ 'uid' ] == $uid ) then
			return true;
	else
			return false;
	end
end

EOT;
		return $this->redis->eval( $script );
		
	}
	
	/**
	 * @param string $strBiz
	 * @param int $iRecordID
	 * return object(stdClass)[97]
     * public 'localTime' => int 1473045341
     * public 'uid' => int 3
	 */
	public function getLockInfo( $strBiz, $iRecordID )
	{
		if( !$this->redis )
			return false;
		
		$strKeyID = self::LOCK_EX_PREFIX . $strBiz . '_' . $iRecordID;
		
		$strJson = $this->redis->get( $strKeyID );
		
		if( $strJson )
		{
			$jsonData = json_decode( $strJson );
			$jsonData->recordID = $iRecordID;
			$jsonData->biz = $strBiz;
			
			return $jsonData;
		}
		
		return false;
	}
// 	public function setDI(\Phalcon\DiInterface $dependencyInjector) 
// 	{
// 		$this->_di = $dependencyInjector;
// 	}

// 	public function getDI() 
// 	{
// 		return $this->_di;
// 	}

}



用法

		$rl = new RedLock( $this->nredis );
		$strUID = 'asdflkjasdflkjq;we13123412351asdfasdf';
		
		var_dump( $rl->lockEx( 'audit_narrator', 9, $strUID, 300 ));//true ttl 300
		var_dump( $rl->lockEx( 'audit_narrator', 9, $strUID, 500 ));//true ttl 500
		var_dump( $rl->lockEx( 'audit_narrator', 9, 2, 500 ));//false		
		var_dump( $rl->lockEx( 'audit_narrator', 9, 3, 500 ));//true
		
		var_dump( $rl->unlockEx( 'audit_narrator', 9,3 ));//true
		var_dump( $rl->lockEx( 'audit_narrator', 9, 3, 900 ));//true




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值