<?php /** * * */ class Lock { /** * * * @param string $key * @return string */ static protected function getKey( $key ) { return $key; } /** * true, * * @param string $key - * @param float $timeWait - * @param float $maxExecuteTime - * @return bool */ static public function getLock( $key, $timeWait = 0, $maxExecuteTime = 3600 ) { throw new Lock_Exception('Not defined method getLock'); return false; } /** * - * * @return string */ static protected function getCurrentProcessId() { static $myProcessId = false; if ( $myProcessId === false ) { $uname = posix_uname(); $mypid = getmypid(); $myProcessId = $uname['nodename'] . '_' . $mypid; } return $myProcessId; } /** * * * @param string $key - * @param float $delayAfter - * @return bool */ static public function releaseLock( $key, $delayAfter = 0 ) { throw new Lock_Exception('Not defined method releaseLock'); return false; } /** * * * @param string $key - * @param float $timeProlongate - * @return bool - false, */ static public function prolongate( $key, $timeProlongate ) { throw new Lock_Exception('Not defined method prolongate'); return false; } } class Lock_Exception extends Exception { } class Timeout_Lock_Exception extends Lock_Exception { } class LostLock_Timeout_Lock_Exception extends Timeout_Lock_Exception { }
<?php /** * Redis * */ class RedisLock extends Lock { /** * noSQL * * @param string $key * @return string */ static protected function getKey( $key ) { // lock@ return 'lock@'.$key; } /** * true, * * @param string $key - * @param float $timeWait - * @param float $maxExecuteTime - * @param integer $policy - : * 0 - , , * 1 - 10, * @return bool */ static public function getLock( $key, $timeWait = 0, $maxExecuteTime = 3600, $policy = 0 ) { /** * , */ $timeStop = microtime(true) + $timeWait; // Yii Redis Rediska $rediska = Yii::app()->rediskaConnection->connect(); while ( true ) { $currentTime = microtime(true); if ( $policy == 0 ) { /** * , */ $expireAt = $rediska->getFromHash( self::getKey($key), 'expireAt' ); /** * , , , * */ if ( $expireAt > $timeStop ) { return false; } /** * , , */ elseif ( $expireAt > $currentTime ) { usleep( 1000000 * intval($expireAt - $currentTime) ); $currentTime = microtime(true); } } elseif ( $policy == 1 ) { /** * , */ $expireAt = $rediska->getFromHash( self::getKey($key), 'expireAt' ); while ( $expireAt > $timeStop || $expireAt > $currentTime ) { usleep( 10000 ); /** * , */ $expireAt = $rediska->getFromHash( self::getKey($key), 'expireAt' ); $currentTime = microtime(true); if ( $currentTime >= $timeStop ) { return false; } } } $getLock = false; /** * * getConnectionByKeyName , Redis */ $transaction = $rediska->transaction( $rediska->getConnectionByKeyName( self::getKey($key) ) ); $transaction->watch( self::getKey($key) ); $arData = $rediska->getHash( self::getKey($key) ); // $daddy = isset($arData['daddy']) ? $arData['daddy'] : ''; // $expireAt = isset($arData['expireAt']) ? $arData['expireAt'] : 0; /** * , , * */ if ( $daddy != self::getCurrentProcessId() && $expireAt < $currentTime ) { $transaction->setToHash( self::getKey($key), array( 'daddy' => self::getCurrentProcessId(), 'expireAt' => $currentTime + $maxExecuteTime ) ); $transaction->expire( self::getKey($key), ceil($currentTime + $maxExecuteTime), true ); try { $transaction->execute(); $getLock = 1; } catch ( Rediska_Transaction_Exception $e ) { /** * */ $getLock = false; } } else { $getLock = false; $transaction->discard(); } /** * */ if ( $getLock != 1 ) { // HSETNX $getLock = $rediska->setToHash( self::getKey($key), 'daddy', self::getCurrentProcessId(), false ); } /** * */ if ( $getLock == 1 ) { /** * , */ $rediska->setToHash(self::getKey($key), 'expireAt', $currentTime + $maxExecuteTime); $rediska->expire(self::getKey($key), ceil($currentTime + $maxExecuteTime), true); return true; } else { /** * , */ if ( $timeStop > $currentTime ) { usleep(20000); } /** * , */ else { return false; } } } } /** * * * @param string $key - * @param float $delayAfter - * @return bool */ static public function releaseLock( $key, $delayAfter = 0 ) { $currentTime = microtime(true); $rediska = Yii::app()->rediskaConnection->connect(); $transaction = $rediska->transaction( $rediska->getConnectionByKeyName( self::getKey($key) ) ); $transaction->watch( self::getKey($key) ); $arData = $rediska->getHash( self::getKey($key) ); if ( is_array($arData) && isset($arData['daddy']) && isset($arData['expireAt']) ) { $daddy = $arData['daddy']; $expireAt = $arData['expireAt']; } else { $daddy = false; $expireAt = 0; } /** * , */ if ( $daddy == self::getCurrentProcessId() ) { $transaction->setToHash(self::getKey($key), 'expireAt', $currentTime + $delayAfter); $transaction->expire(self::getKey($key), ceil($currentTime + $delayAfter), true); $transaction->deleteFromHash( self::getKey($key), 'daddy' ); /** * */ try { $transaction->execute(); $result = true; } catch (Rediska_Transaction_Exception $e) { $result = false; } } else { $transaction->discard(); $result = false; } // if ( $expireAt < $currentTime ) { if ( $result ) { /** * , */ throw new Timeout_Lock_Exception('Timeout Lock on release'); } else { /** * */ throw new LostLock_Timeout_Lock_Exception('Timeout Lock and it was lost before release'); } } return $result; } /** * * * @param string $key - * @param float $timeProlongate - * @return bool - false, */ static public function prolongate( $key, $timeProlongate ) { $rediska = Yii::app()->rediskaConnection->connect(); $transaction = $rediska->transaction( $rediska->getConnectionByKeyName( self::getKey($key) ) ); $transaction->watch( self::getKey($key) ); $arData = $rediska->getHash( self::getKey($key) ); $daddy = $arData['daddy']; $expireAt = $arData['expireAt']; $currentTime = microtime(true); $result = false; if ( $daddy == self::getCurrentProcessId() ) { $transaction->setToHash( self::getKey($key), 'expireAt', $currentTime + $timeProlongate ); $transaction->expire(self::getKey($key), ceil($currentTime + $timeProlongate), true); try { $transaction->execute(); $result = true; } catch (Rediska_Transaction_Exception $e) { $result = false; } } else { $transaction->discard(); $result = false; } if ( $expireAt < $currentTime ) { if ( $result ) { throw new Timeout_Lock_Exception('Timeout Lock on prolongate'); } else { throw new LostLock_Timeout_Lock_Exception('Timeout Lock and Lost them on prolongate'); } } return $result; } }
$lockKey = 'cron-report'; $timeWait = 0; $timeLock = 3600; if ( RedisLock::getLock( $lockKey, $timeWait, $timeLock ) ) { // , ... // , try { RedisLock::releaseLock( $lockKey, 0 ); echo 'Ok'; } catch ( Timeout_Lock_Exception $e ) { // // , , echo 'Timeout_Lock_Exception ' . ( $endTime - $currentTime ); } catch ( LostLock_Timeout_Lock_Exception $e ) { // // , , echo 'LostLock_Timeout_Lock_Exception' . ( $endTime - $currentTime ); } }
Source: https://habr.com/ru/post/158799/