X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;ds=sidebyside;f=includes%2Fobjectcache%2FRedisBagOStuff.php;h=ed0aaa23684f747d6ab593682b191eaad95fd631;hb=02f609e06889a172c64a863481cc5f692f86e64a;hp=de3511df977d9ec3e5d3c450210b065369690e0d;hpb=b3b50d729498435b8b9370ecddea73d082f9b9e6;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/objectcache/RedisBagOStuff.php b/includes/objectcache/RedisBagOStuff.php index de3511df97..a9af9b126b 100644 --- a/includes/objectcache/RedisBagOStuff.php +++ b/includes/objectcache/RedisBagOStuff.php @@ -25,6 +25,8 @@ class RedisBagOStuff extends BagOStuff { protected $redisPool; /** @var array List of server names */ protected $servers; + /** @var array Map of (tag => server name) */ + protected $serverTagMap; /** @var bool */ protected $automaticFailover; @@ -34,7 +36,8 @@ class RedisBagOStuff extends BagOStuff { * - servers: An array of server names. A server name may be a hostname, * a hostname/port combination or the absolute path of a UNIX socket. * If a hostname is specified but no port, the standard port number - * 6379 will be used. Required. + * 6379 will be used. Arrays keys can be used to specify the tag to + * hash on in place of the host/port. Required. * * - connectTimeout: The timeout for new connections, in seconds. Optional, * default is 1 second. @@ -66,6 +69,10 @@ class RedisBagOStuff extends BagOStuff { $this->redisPool = RedisConnectionPool::singleton( $redisConf ); $this->servers = $params['servers']; + foreach ( $this->servers as $key => $server ) { + $this->serverTagMap[is_int( $key ) ? $server : $key] = $server; + } + if ( isset( $params['automaticFailover'] ) ) { $this->automaticFailover = $params['automaticFailover']; } else { @@ -73,7 +80,7 @@ class RedisBagOStuff extends BagOStuff { } } - public function get( $key, &$casToken = null ) { + public function get( $key, &$casToken = null, $flags = 0 ) { list( $server, $conn ) = $this->getConnection( $key ); if ( !$conn ) { @@ -167,7 +174,7 @@ class RedisBagOStuff extends BagOStuff { return $result; } - public function getMulti( array $keys ) { + public function getMulti( array $keys, $flags = 0 ) { $batches = array(); $conns = array(); @@ -257,8 +264,6 @@ class RedisBagOStuff extends BagOStuff { return $result; } - - public function add( $key, $value, $expiry = 0 ) { list( $server, $conn ) = $this->getConnection( $key ); @@ -270,6 +275,7 @@ class RedisBagOStuff extends BagOStuff { if ( $expiry ) { $conn->multi(); $conn->setnx( $key, $this->serialize( $value ) ); + // @FIXME: this always bumps the TTL; use Redis 2.8 or Lua $conn->expire( $key, $expiry ); $result = ( $conn->exec() == array( true, true ) ); } else { @@ -306,6 +312,7 @@ class RedisBagOStuff extends BagOStuff { return null; } try { + // @FIXME: on races, the key may have a 0 TTL $result = $conn->incrBy( $key, $value ); } catch ( RedisException $e ) { $result = false; @@ -324,6 +331,14 @@ class RedisBagOStuff extends BagOStuff { return $this->mergeViaCas( $key, $callback, $exptime, $attempts ); } + public function modifySimpleRelayEvent( array $event ) { + if ( array_key_exists( 'val', $event ) ) { + $event['val'] = serialize( $event['val'] ); // this class uses PHP serialization + } + + return $event; + } + /** * @param mixed $data * @return string @@ -348,26 +363,62 @@ class RedisBagOStuff extends BagOStuff { * @return array (server, RedisConnRef) or (false, false) */ protected function getConnection( $key ) { - if ( count( $this->servers ) === 1 ) { - $candidates = $this->servers; - } else { - $candidates = $this->servers; + $candidates = array_keys( $this->serverTagMap ); + + if ( count( $this->servers ) > 1 ) { ArrayUtils::consistentHashSort( $candidates, $key, '/' ); if ( !$this->automaticFailover ) { $candidates = array_slice( $candidates, 0, 1 ); } } - foreach ( $candidates as $server ) { + while ( ( $tag = array_shift( $candidates ) ) !== null ) { + $server = $this->serverTagMap[$tag]; $conn = $this->redisPool->getConnection( $server ); - if ( $conn ) { - return array( $server, $conn ); + if ( !$conn ) { + continue; } + + // If automatic failover is enabled, check that the server's link + // to its master (if any) is up -- but only if there are other + // viable candidates left to consider. + if ( $this->automaticFailover && $candidates ) { + try { + if ( $this->getMasterLinkStatus( $conn ) === 'down' ) { + // If the master cannot be reached, fail-over to the next server. + // If masters are in data-center A, and slaves in data-center B, + // this helps avoid the case were fail-over happens in A but not + // to the corresponding server in B (e.g. read/write mismatch). + continue; + } + } catch ( RedisException $e ) { + // Server is not accepting commands + $this->handleException( $conn, $e ); + continue; + } + } + + return array( $server, $conn ); } + $this->setLastError( BagOStuff::ERR_UNREACHABLE ); + return array( false, false ); } + /** + * Check the master link status of a Redis server that is configured as a slave. + * @param RedisConnRef $conn + * @return string|null Master link status (either 'up' or 'down'), or null + * if the server is not a slave. + */ + protected function getMasterLinkStatus( RedisConnRef $conn ) { + $info = $conn->info(); + return isset( $info['master_link_status'] ) + ? $info['master_link_status'] + : null; + } + /** * Log a fatal error * @param string $msg