MediaWiki  REL1_21
RedisBagOStuff.php
Go to the documentation of this file.
00001 <?php
00023 class RedisBagOStuff extends BagOStuff {
00025         protected $redisPool;
00027         protected $servers;
00029         protected $automaticFailover;
00030 
00057         function __construct( $params ) {
00058                 $redisConf = array( 'serializer' => 'php' );
00059                 foreach ( array( 'connectTimeout', 'persistent', 'password' ) as $opt ) {
00060                         if ( isset( $params[$opt] ) ) {
00061                                 $redisConf[$opt] = $params[$opt];
00062                         }
00063                 }
00064                 $this->redisPool = RedisConnectionPool::singleton( $redisConf );
00065 
00066                 $this->servers = $params['servers'];
00067                 if ( isset( $params['automaticFailover'] ) ) {
00068                         $this->automaticFailover = $params['automaticFailover'];
00069                 } else {
00070                         $this->automaticFailover = true;
00071                 }
00072         }
00073 
00074         public function get( $key, &$casToken = null ) {
00075                 wfProfileIn( __METHOD__ );
00076                 list( $server, $conn ) = $this->getConnection( $key );
00077                 if ( !$conn ) {
00078                         wfProfileOut( __METHOD__ );
00079                         return false;
00080                 }
00081                 try {
00082                         $result = $conn->get( $key );
00083                 } catch ( RedisException $e ) {
00084                         $result = false;
00085                         $this->handleException( $server, $conn, $e );
00086                 }
00087                 $casToken = $result;
00088                 $this->logRequest( 'get', $key, $server, $result );
00089                 wfProfileOut( __METHOD__ );
00090                 return $result;
00091         }
00092 
00093         public function set( $key, $value, $expiry = 0 ) {
00094                 wfProfileIn( __METHOD__ );
00095                 list( $server, $conn ) = $this->getConnection( $key );
00096                 if ( !$conn ) {
00097                         wfProfileOut( __METHOD__ );
00098                         return false;
00099                 }
00100                 $expiry = $this->convertToRelative( $expiry );
00101                 try {
00102                         if ( !$expiry ) {
00103                                 // No expiry, that is very different from zero expiry in Redis
00104                                 $result = $conn->set( $key, $value );
00105                         } else {
00106                                 $result = $conn->setex( $key, $expiry, $value );
00107                         }
00108                 } catch ( RedisException $e ) {
00109                         $result = false;
00110                         $this->handleException( $server, $conn, $e );
00111                 }
00112 
00113                 $this->logRequest( 'set', $key, $server, $result );
00114                 wfProfileOut( __METHOD__ );
00115                 return $result;
00116         }
00117 
00118         public function cas( $casToken, $key, $value, $expiry = 0 ) {
00119                 wfProfileIn( __METHOD__ );
00120                 list( $server, $conn ) = $this->getConnection( $key );
00121                 if ( !$conn ) {
00122                         wfProfileOut( __METHOD__ );
00123                         return false;
00124                 }
00125                 $expiry = $this->convertToRelative( $expiry );
00126                 try {
00127                         $conn->watch( $key );
00128 
00129                         if ( $this->get( $key ) !== $casToken ) {
00130                                 wfProfileOut( __METHOD__ );
00131                                 return false;
00132                         }
00133 
00134                         $conn->multi();
00135 
00136                         if ( !$expiry ) {
00137                                 // No expiry, that is very different from zero expiry in Redis
00138                                 $conn->set( $key, $value );
00139                         } else {
00140                                 $conn->setex( $key, $expiry, $value );
00141                         }
00142 
00143                         $result = $conn->exec();
00144                 } catch ( RedisException $e ) {
00145                         $result = false;
00146                         $this->handleException( $server, $conn, $e );
00147                 }
00148 
00149                 $this->logRequest( 'cas', $key, $server, $result );
00150                 wfProfileOut( __METHOD__ );
00151                 return $result;
00152         }
00153 
00154         public function delete( $key, $time = 0 ) {
00155                 wfProfileIn( __METHOD__ );
00156                 list( $server, $conn ) = $this->getConnection( $key );
00157                 if ( !$conn ) {
00158                         wfProfileOut( __METHOD__ );
00159                         return false;
00160                 }
00161                 try {
00162                         $conn->delete( $key );
00163                         // Return true even if the key didn't exist
00164                         $result = true;
00165                 } catch ( RedisException $e ) {
00166                         $result = false;
00167                         $this->handleException( $server, $conn, $e );
00168                 }
00169                 $this->logRequest( 'delete', $key, $server, $result );
00170                 wfProfileOut( __METHOD__ );
00171                 return $result;
00172         }
00173 
00174         public function getMulti( array $keys ) {
00175                 wfProfileIn( __METHOD__ );
00176                 $batches = array();
00177                 $conns = array();
00178                 foreach ( $keys as $key ) {
00179                         list( $server, $conn ) = $this->getConnection( $key );
00180                         if ( !$conn ) {
00181                                 continue;
00182                         }
00183                         $conns[$server] = $conn;
00184                         $batches[$server][] = $key;
00185                 }
00186                 $result = array();
00187                 foreach ( $batches as $server => $batchKeys ) {
00188                         $conn = $conns[$server];
00189                         try {
00190                                 $conn->multi( Redis::PIPELINE );
00191                                 foreach ( $batchKeys as $key ) {
00192                                         $conn->get( $key );
00193                                 }
00194                                 $batchResult = $conn->exec();
00195                                 if ( $batchResult === false ) {
00196                                         $this->debug( "multi request to $server failed" );
00197                                         continue;
00198                                 }
00199                                 foreach ( $batchResult as $i => $value ) {
00200                                         if ( $value !== false ) {
00201                                                 $result[$batchKeys[$i]] = $value;
00202                                         }
00203                                 }
00204                         } catch ( RedisException $e ) {
00205                                 $this->handleException( $server, $conn, $e );
00206                         }
00207                 }
00208 
00209                 $this->debug( "getMulti for " . count( $keys ) . " keys " .
00210                         "returned " . count( $result ) . " results" );
00211                 wfProfileOut( __METHOD__ );
00212                 return $result;
00213         }
00214 
00215         public function add( $key, $value, $expiry = 0 ) {
00216                 wfProfileIn( __METHOD__ );
00217                 list( $server, $conn ) = $this->getConnection( $key );
00218                 if ( !$conn ) {
00219                         wfProfileOut( __METHOD__ );
00220                         return false;
00221                 }
00222                 $expiry = $this->convertToRelative( $expiry );
00223                 try {
00224                         $result = $conn->setnx( $key, $value );
00225                         if ( $result && $expiry ) {
00226                                 $conn->expire( $key, $expiry );
00227                         }
00228                 } catch ( RedisException $e ) {
00229                         $result = false;
00230                         $this->handleException( $server, $conn, $e );
00231                 }
00232                 $this->logRequest( 'add', $key, $server, $result );
00233                 wfProfileOut( __METHOD__ );
00234                 return $result;
00235         }
00236 
00241         public function replace( $key, $value, $expiry = 0 ) {
00242                 wfProfileIn( __METHOD__ );
00243                 list( $server, $conn ) = $this->getConnection( $key );
00244                 if ( !$conn ) {
00245                         wfProfileOut( __METHOD__ );
00246                         return false;
00247                 }
00248                 if ( !$conn->exists( $key ) ) {
00249                         wfProfileOut( __METHOD__ );
00250                         return false;
00251                 }
00252 
00253                 $expiry = $this->convertToRelative( $expiry );
00254                 try {
00255                         if ( !$expiry ) {
00256                                 $result = $conn->set( $key, $value );
00257                         } else {
00258                                 $result = $conn->setex( $key, $expiry, $value );
00259                         }
00260                 } catch ( RedisException $e ) {
00261                         $result = false;
00262                         $this->handleException( $server, $conn, $e );
00263                 }
00264 
00265                 $this->logRequest( 'replace', $key, $server, $result );
00266                 wfProfileOut( __METHOD__ );
00267                 return $result;
00268         }
00269 
00279         public function incr( $key, $value = 1 ) {
00280                 wfProfileIn( __METHOD__ );
00281                 list( $server, $conn ) = $this->getConnection( $key );
00282                 if ( !$conn ) {
00283                         wfProfileOut( __METHOD__ );
00284                         return false;
00285                 }
00286                 if ( !$conn->exists( $key ) ) {
00287                         wfProfileOut( __METHOD__ );
00288                         return null;
00289                 }
00290                 try {
00291                         $result = $conn->incrBy( $key, $value );
00292                 } catch ( RedisException $e ) {
00293                         $result = false;
00294                         $this->handleException( $server, $conn, $e );
00295                 }
00296 
00297                 $this->logRequest( 'incr', $key, $server, $result );
00298                 wfProfileOut( __METHOD__ );
00299                 return $result;
00300         }
00301 
00306         protected function getConnection( $key ) {
00307                 if ( count( $this->servers ) === 1 ) {
00308                         $candidates = $this->servers;
00309                 } else {
00310                         $candidates = $this->servers;
00311                         ArrayUtils::consistentHashSort( $candidates, $key, '/' );
00312                         if ( !$this->automaticFailover ) {
00313                                 $candidates = array_slice( $candidates, 0, 1 );
00314                         }
00315                 }
00316 
00317                 foreach ( $candidates as $server ) {
00318                         $conn = $this->redisPool->getConnection( $server );
00319                         if ( $conn ) {
00320                                 return array( $server, $conn );
00321                         }
00322                 }
00323                 return array( false, false );
00324         }
00325 
00329         protected function logError( $msg ) {
00330                 wfDebugLog( 'redis', "Redis error: $msg\n" );
00331         }
00332 
00339         protected function handleException( $server, RedisConnRef $conn, $e ) {
00340                 $this->redisPool->handleException( $server, $conn, $e );
00341         }
00342 
00346         public function logRequest( $method, $key, $server, $result ) {
00347                 $this->debug( "$method $key on $server: " .
00348                         ( $result === false ? "failure" : "success" ) );
00349         }
00350 }