Exemplo n.º 1
0
 /**
  * Save some string datas into a cache record
  *
  * Note : $data is always "string" (serialization is done by the
  * core not by the backend)
  *
  * @param  string $data             Datas to cache
  * @param  string $id               Cache id
  * @param  array  $tags             Array of strings, the cache record will be tagged by each string entry
  * @param  bool|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  * @throws CredisException
  * @return boolean True if no problem
  */
 public function save($data, $id, $tags = array(), $specificLifetime = false)
 {
     if (!is_array($tags)) {
         $tags = $tags ? array($tags) : array();
     }
     $lifetime = $this->getLifetime($specificLifetime);
     // Get list of tags previously assigned
     $oldTags = $this->_decodeData($this->_redis->hGet(self::PREFIX_KEY . $id, self::FIELD_TAGS));
     $oldTags = $oldTags ? explode(',', $oldTags) : array();
     $this->_redis->pipeline()->multi();
     // Set the data
     $result = $this->_redis->hMSet(self::PREFIX_KEY . $id, array(self::FIELD_DATA => $this->_encodeData($data, $this->_compressData), self::FIELD_TAGS => $this->_encodeData(implode(',', $tags), $this->_compressTags), self::FIELD_MTIME => time(), self::FIELD_INF => $lifetime ? 0 : 1));
     if (!$result) {
         throw new CredisException("Could not set cache key {$id}");
     }
     // Set expiration if specified
     if ($lifetime) {
         $this->_redis->expire(self::PREFIX_KEY . $id, min($lifetime, self::MAX_LIFETIME));
     }
     // Process added tags
     if ($tags) {
         // Update the list with all the tags
         $this->_redis->sAdd(self::SET_TAGS, $tags);
         // Update the id list for each tag
         foreach ($tags as $tag) {
             $this->_redis->sAdd(self::PREFIX_TAG_IDS . $tag, $id);
         }
     }
     // Process removed tags
     if ($remTags = $oldTags ? array_diff($oldTags, $tags) : FALSE) {
         // Update the id list for each tag
         foreach ($remTags as $tag) {
             $this->_redis->sRem(self::PREFIX_TAG_IDS . $tag, $id);
         }
     }
     // Update the list with all the ids
     if ($this->_notMatchingTags) {
         $this->_redis->sAdd(self::SET_IDS, $id);
     }
     $this->_redis->exec();
     return TRUE;
 }
Exemplo n.º 2
0
 /**
  * Fetch session data
  *
  * @param string $sessionId
  * @return string
  */
 public function read($sessionId)
 {
     $this->profilerStart(__METHOD__);
     // Get lock on session. Increment the "lock" field and if the new value is 1, we have the lock.
     $sessionId = self::SESSION_PREFIX . $sessionId;
     $tries = $waiting = $lock = 0;
     $lockPid = $oldLockPid = NULL;
     // Restart waiting for lock when current lock holder changes
     $detectZombies = FALSE;
     $breakAfter = $this->_getBreakAfter();
     if ($this->_logLevel >= \Zend_Log::WARN) {
         $timeStart = microtime(true);
     }
     if ($this->_logLevel >= \Zend_Log::DEBUG) {
         $this->_log(sprintf("Attempting to take lock on ID %s", $sessionId));
     }
     $this->_redis->select($this->_dbNum);
     while ($this->_useLocking) {
         // Increment lock value for this session and retrieve the new value
         $oldLock = $lock;
         $lock = $this->_redis->hIncrBy($sessionId, 'lock', 1);
         // Get the pid of the process that has the lock
         if ($lock != 1 && $tries + 1 >= $breakAfter) {
             $lockPid = $this->_redis->hGet($sessionId, 'pid');
         }
         // If we got the lock, update with our pid and reset lock and expiration
         if ($lock == 1 || $tries >= $breakAfter && $oldLockPid == $lockPid) {
             $this->_hasLock = TRUE;
             break;
         } else {
             if (!$waiting) {
                 $i = 0;
                 do {
                     $waiting = $this->_redis->hIncrBy($sessionId, 'wait', 1);
                 } while (++$i < $this->_maxConcurrency && $waiting < 1);
             } else {
                 // Detect broken sessions (e.g. caused by fatal errors)
                 if ($detectZombies) {
                     $detectZombies = FALSE;
                     if ($lock > $oldLock && $lock + 1 < $oldLock + $waiting) {
                         // Reset session to fresh state
                         if ($this->_logLevel >= \Zend_Log::INFO) {
                             $this->_log(sprintf("Detected zombie waiter after %.5f seconds for ID %s (%d waiting)\n  %s (%s - %s)", microtime(true) - $timeStart, $sessionId, $waiting, Mage::app()->getRequest()->getRequestUri(), Mage::app()->getRequest()->getClientIp(), Mage::app()->getRequest()->getHeader('User-Agent')), \Zend_Log::INFO);
                         }
                         $waiting = $this->_redis->hIncrBy($sessionId, 'wait', -1);
                         continue;
                     }
                 }
                 // Limit concurrent lock waiters to prevent server resource hogging
                 if ($waiting >= $this->_maxConcurrency) {
                     // Overloaded sessions get 503 errors
                     $this->_redis->hIncrBy($sessionId, 'wait', -1);
                     $this->_sessionWritten = TRUE;
                     // Prevent session from getting written
                     $writes = $this->_redis->hGet($sessionId, 'writes');
                     if ($this->_logLevel >= \Zend_Log::WARN) {
                         $this->_log(sprintf("Session concurrency exceeded for ID %s; displaying HTTP 503 (%s waiting, %s total requests)\n  %s (%s - %s)", $sessionId, $waiting, $writes, Mage::app()->getRequest()->getRequestUri(), Mage::app()->getRequest()->getClientIp(), Mage::app()->getRequest()->getHeader('User-Agent')), \Zend_Log::WARN);
                     }
                     $this->_logLevel = -1;
                     // Disable further logging
                     require_once Mage::getBaseDir() . DS . 'errors' . DS . '503.php';
                     exit;
                 }
             }
         }
         $tries++;
         $oldLockPid = $lockPid;
         $sleepTime = self::SLEEP_TIME;
         // Detect dead lock waiters
         if ($tries % self::DETECT_ZOMBIES == 1) {
             $detectZombies = TRUE;
             $sleepTime += 10000;
             // sleep + 0.01 seconds
         }
         // Detect dead lock holder every 10 seconds (only works on same node as lock holder)
         if ($tries % self::DETECT_ZOMBIES == 0) {
             $this->profilerStart(__METHOD__ . '-detect-zombies');
             if ($this->_logLevel >= \Zend_Log::DEBUG) {
                 $this->_log(sprintf("Checking for zombies after %.5f seconds of waiting...", microtime(true) - $timeStart));
             }
             $pid = $this->_redis->hGet($sessionId, 'pid');
             if ($pid && !$this->_pidExists($pid)) {
                 // Allow a live process to get the lock
                 $this->_redis->hSet($sessionId, 'lock', 0);
                 if ($this->_logLevel >= \Zend_Log::INFO) {
                     $this->_log(sprintf("Detected zombie process (%s) for %s (%s waiting)\n  %s (%s - %s)", $pid, $sessionId, $waiting, Mage::app()->getRequest()->getRequestUri(), Mage::app()->getRequest()->getClientIp(), Mage::app()->getRequest()->getHeader('User-Agent')), \Zend_Log::INFO);
                 }
                 $this->profilerStop(__METHOD__ . '-detect-zombies');
                 continue;
             }
             $this->profilerStop(__METHOD__ . '-detect-zombies');
         }
         // Timeout
         if ($tries >= $breakAfter + $this->_failAfter) {
             $this->_hasLock = FALSE;
             if ($this->_logLevel >= \Zend_Log::NOTICE) {
                 $this->_log(sprintf("Giving up on read lock for ID %s after %.5f seconds (%d attempts)", $sessionId, microtime(true) - $timeStart, $tries), \Zend_Log::NOTICE);
             }
             break;
         } else {
             if ($this->_logLevel >= \Zend_Log::DEBUG) {
                 $this->_log(sprintf("Waiting %.2f seconds for lock on ID %s (%d tries, lock pid is %s, %.5f seconds elapsed)", $sleepTime / 1000000, $sessionId, $tries, $lockPid, microtime(true) - $timeStart));
             }
             $this->profilerStart(__METHOD__ . '-wait');
             usleep($sleepTime);
             $this->profilerStop(__METHOD__ . '-wait');
         }
     }
     self::$failedLockAttempts = $tries;
     // Session can be read even if it was not locked by this pid!
     if ($this->_logLevel >= \Zend_Log::DEBUG) {
         $timeStart = microtime(true);
     }
     list($sessionData, $sessionWrites) = $this->_redis->hMGet($sessionId, array('data', 'writes'));
     $this->profilerStop(__METHOD__);
     if ($this->_logLevel >= \Zend_Log::DEBUG) {
         $this->_log(sprintf("Data read for ID %s in %.5f seconds", $sessionId, microtime(true) - $timeStart));
     }
     $this->_sessionWrites = (int) $sessionWrites;
     // This process is no longer waiting for a lock
     if ($tries > 0) {
         $this->_redis->hIncrBy($sessionId, 'wait', -1);
     }
     // This process has the lock, save the pid
     if ($this->_hasLock) {
         $setData = array('pid' => $this->_getPid(), 'lock' => 1);
         // Save request data in session so if a lock is broken we can know which page it was for debugging
         if ($this->_logLevel >= \Zend_Log::INFO) {
             if (empty($_SERVER['REQUEST_METHOD'])) {
                 $setData['req'] = $_SERVER['SCRIPT_NAME'];
             } else {
                 $setData['req'] = "{$_SERVER['REQUEST_METHOD']} {$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']}";
             }
             if ($lock != 1) {
                 $this->_log(sprintf("Successfully broke lock for ID %s after %.5f seconds (%d attempts). Lock: %d\nLast request of broken lock: %s", $sessionId, microtime(true) - $timeStart, $tries, $lock, $this->_redis->hGet($sessionId, 'req')), \Zend_Log::INFO);
             }
         }
     }
     // Set session data and expiration
     $this->_redis->pipeline();
     if (!empty($setData)) {
         $this->_redis->hMSet($sessionId, $setData);
     }
     $this->_redis->expire($sessionId, min($this->getLifeTime(), $this->_maxLifetime));
     $this->_redis->exec();
     // Reset flag in case of multiple session read/write operations
     $this->_sessionWritten = FALSE;
     return $sessionData ? $this->_decodeData($sessionData) : '';
 }
 /**
  * 设置过期时间-时间戳格式
  * @param $id
  * @param $expire
  * @return mixed
  */
 public function expire($id, $expire)
 {
     $key = $this->_getKeyName($id);
     return $this->_client->expire($key, $expire);
 }
 /**
  * Fetch session data
  *
  * @param string $sessionId
  * @return string
  * @throws ConcurrentConnectionsExceededException
  */
 public function read($sessionId)
 {
     // Get lock on session. Increment the "lock" field and if the new value is 1, we have the lock.
     $sessionId = self::SESSION_PREFIX . $sessionId;
     $tries = $waiting = $lock = 0;
     $lockPid = $oldLockPid = null;
     // Restart waiting for lock when current lock holder changes
     $detectZombies = false;
     $breakAfter = $this->_getBreakAfter();
     $timeStart = microtime(true);
     $this->_log(sprintf("Attempting to take lock on ID %s", $sessionId));
     $this->_redis->select($this->_dbNum);
     while ($this->_useLocking) {
         // Increment lock value for this session and retrieve the new value
         $oldLock = $lock;
         $lock = $this->_redis->hIncrBy($sessionId, 'lock', 1);
         // Get the pid of the process that has the lock
         if ($lock != 1 && $tries + 1 >= $breakAfter) {
             $lockPid = $this->_redis->hGet($sessionId, 'pid');
         }
         // If we got the lock, update with our pid and reset lock and expiration
         if ($lock == 1 || $tries >= $breakAfter && $oldLockPid == $lockPid) {
             $this->_hasLock = true;
             break;
         } else {
             if (!$waiting) {
                 $i = 0;
                 do {
                     $waiting = $this->_redis->hIncrBy($sessionId, 'wait', 1);
                 } while (++$i < $this->_maxConcurrency && $waiting < 1);
             } else {
                 // Detect broken sessions (e.g. caused by fatal errors)
                 if ($detectZombies) {
                     $detectZombies = false;
                     // Lock shouldn't be less than old lock (another process broke the lock)
                     if ($lock > $oldLock && $lock + 1 < $oldLock + $waiting) {
                         // Reset session to fresh state
                         $this->_log(sprintf("Detected zombie waiter after %.5f seconds for ID %s (%d waiting)", microtime(true) - $timeStart, $sessionId, $waiting), LoggerInterface::INFO);
                         $waiting = $this->_redis->hIncrBy($sessionId, 'wait', -1);
                         continue;
                     }
                 }
                 // Limit concurrent lock waiters to prevent server resource hogging
                 if ($waiting >= $this->_maxConcurrency) {
                     // Overloaded sessions get 503 errors
                     $this->_redis->hIncrBy($sessionId, 'wait', -1);
                     $this->_sessionWritten = true;
                     // Prevent session from getting written
                     $writes = $this->_redis->hGet($sessionId, 'writes');
                     $this->_log(sprintf('Session concurrency exceeded for ID %s; displaying HTTP 503 (%s waiting, %s total ' . 'requests)', $sessionId, $waiting, $writes), LoggerInterface::WARNING);
                     throw new ConcurrentConnectionsExceededException();
                 }
             }
         }
         $tries++;
         $oldLockPid = $lockPid;
         $sleepTime = self::SLEEP_TIME;
         // Detect dead lock waiters
         if ($tries % self::DETECT_ZOMBIES == 1) {
             $detectZombies = true;
             $sleepTime += 10000;
             // sleep + 0.01 seconds
         }
         // Detect dead lock holder every 10 seconds (only works on same node as lock holder)
         if ($tries % self::DETECT_ZOMBIES == 0) {
             $this->_log(sprintf("Checking for zombies after %.5f seconds of waiting...", microtime(true) - $timeStart));
             $pid = $this->_redis->hGet($sessionId, 'pid');
             if ($pid && !$this->_pidExists($pid)) {
                 // Allow a live process to get the lock
                 $this->_redis->hSet($sessionId, 'lock', 0);
                 $this->_log(sprintf("Detected zombie process (%s) for %s (%s waiting)", $pid, $sessionId, $waiting), LoggerInterface::INFO);
                 continue;
             }
         }
         // Timeout
         if ($tries >= $breakAfter + $this->_failAfter) {
             $this->_hasLock = false;
             $this->_log(sprintf('Giving up on read lock for ID %s after %.5f seconds (%d attempts)', $sessionId, microtime(true) - $timeStart, $tries), LoggerInterface::NOTICE);
             break;
         } else {
             $this->_log(sprintf("Waiting %.2f seconds for lock on ID %s (%d tries, lock pid is %s, %.5f seconds elapsed)", $sleepTime / 1000000, $sessionId, $tries, $lockPid, microtime(true) - $timeStart));
             usleep($sleepTime);
         }
     }
     $this->failedLockAttempts = $tries;
     // Session can be read even if it was not locked by this pid!
     $timeStart2 = microtime(true);
     list($sessionData, $sessionWrites) = $this->_redis->hMGet($sessionId, array('data', 'writes'));
     $this->_log(sprintf("Data read for ID %s in %.5f seconds", $sessionId, microtime(true) - $timeStart2));
     $this->_sessionWrites = (int) $sessionWrites;
     // This process is no longer waiting for a lock
     if ($tries > 0) {
         $this->_redis->hIncrBy($sessionId, 'wait', -1);
     }
     // This process has the lock, save the pid
     if ($this->_hasLock) {
         $setData = array('pid' => $this->_getPid(), 'lock' => 1);
         // Save request data in session so if a lock is broken we can know which page it was for debugging
         if (empty($_SERVER['REQUEST_METHOD'])) {
             $setData['req'] = $_SERVER['SCRIPT_NAME'];
         } else {
             $setData['req'] = "{$_SERVER['REQUEST_METHOD']} {$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']}";
         }
         if ($lock != 1) {
             $this->_log(sprintf("Successfully broke lock for ID %s after %.5f seconds (%d attempts). Lock: %d\nLast request of '\n                            . 'broken lock: %s", $sessionId, microtime(true) - $timeStart, $tries, $lock, $this->_redis->hGet($sessionId, 'req')), LoggerInterface::INFO);
         }
     }
     // Set session data and expiration
     $this->_redis->pipeline();
     if (!empty($setData)) {
         $this->_redis->hMSet($sessionId, $setData);
     }
     $this->_redis->expire($sessionId, 3600 * 6);
     // Expiration will be set to correct value when session is written
     $this->_redis->exec();
     // Reset flag in case of multiple session read/write operations
     $this->_sessionWritten = false;
     return $sessionData ? (string) $this->_decodeData($sessionData) : '';
 }