基于redis的分布式互斥锁

来源:互联网 发布:查看linux下的用户 编辑:程序博客网 时间:2024/06/05 16:07

我们在进行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 = <<<EOTlocal ttl = redis.call( 'ttl', $strKeyID );if( ttl >= $ttl ) thenlocal strVal = redis.call( 'get', $strKeyID );local objVal = cjson.decode( strVal ); if objVal['uid'] == $uid thenreturn true;elsereturn false;endelseif ttl < 0 thenlocal val = {};val["uid"] = $uid;val["localTime"] = $curTime;local setRes = redis.call( 'setnx', $strKeyID, cjson.encode( val ) );if setRes == 1 thenreturn redis.call( 'expire', $strKeyID, $ttl );endelseif ttl > 0 and ttl < $ttl thenlocal strVal = redis.call( 'get', $strKeyID );local objVal = cjson.decode( strVal ); if objVal['uid'] == $uid thenreturn redis.call( 'expire', $strKeyID, $ttl );elsereturn false;endelsereturn false;endEOT;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 = <<<EOTlocal lockData = redis.call( 'get', $strKeyID );if lockData == false thenreturn true;elselocal objData = cjson.decode( lockData );if( objData[ 'uid' ] == $uid ) thenreturn redis.call( 'del', $strKeyID );endreturn false;endEOT;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 = <<<EOTlocal lockData = redis.call( 'get', $strKeyID );if lockData == false thenreturn false;elselocal objData = cjson.decode( lockData );if( objData[ 'uid' ] == $uid ) thenreturn true;elsereturn false;endendEOT;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 300var_dump( $rl->lockEx( 'audit_narrator', 9, $strUID, 500 ));//true ttl 500var_dump( $rl->lockEx( 'audit_narrator', 9, 2, 500 ));//falsevar_dump( $rl->lockEx( 'audit_narrator', 9, 3, 500 ));//truevar_dump( $rl->unlockEx( 'audit_narrator', 9,3 ));//truevar_dump( $rl->lockEx( 'audit_narrator', 9, 3, 900 ));//true




0 0
原创粉丝点击