MediaWiki  REL1_22
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             /*
00144              * multi()/exec() (transactional mode) allows multiple values to
00145              * be set/get at once and will return an array of results, in
00146              * the order they were set/get. In this case, we only set 1
00147              * value, which should (in case of success) result in true.
00148              */
00149             $result = ( $conn->exec() == array( true ) );
00150         } catch ( RedisException $e ) {
00151             $result = false;
00152             $this->handleException( $server, $conn, $e );
00153         }
00154 
00155         $this->logRequest( 'cas', $key, $server, $result );
00156         wfProfileOut( __METHOD__ );
00157         return $result;
00158     }
00159 
00160     public function delete( $key, $time = 0 ) {
00161         wfProfileIn( __METHOD__ );
00162         list( $server, $conn ) = $this->getConnection( $key );
00163         if ( !$conn ) {
00164             wfProfileOut( __METHOD__ );
00165             return false;
00166         }
00167         try {
00168             $conn->delete( $key );
00169             // Return true even if the key didn't exist
00170             $result = true;
00171         } catch ( RedisException $e ) {
00172             $result = false;
00173             $this->handleException( $server, $conn, $e );
00174         }
00175         $this->logRequest( 'delete', $key, $server, $result );
00176         wfProfileOut( __METHOD__ );
00177         return $result;
00178     }
00179 
00180     public function getMulti( array $keys ) {
00181         wfProfileIn( __METHOD__ );
00182         $batches = array();
00183         $conns = array();
00184         foreach ( $keys as $key ) {
00185             list( $server, $conn ) = $this->getConnection( $key );
00186             if ( !$conn ) {
00187                 continue;
00188             }
00189             $conns[$server] = $conn;
00190             $batches[$server][] = $key;
00191         }
00192         $result = array();
00193         foreach ( $batches as $server => $batchKeys ) {
00194             $conn = $conns[$server];
00195             try {
00196                 $conn->multi( Redis::PIPELINE );
00197                 foreach ( $batchKeys as $key ) {
00198                     $conn->get( $key );
00199                 }
00200                 $batchResult = $conn->exec();
00201                 if ( $batchResult === false ) {
00202                     $this->debug( "multi request to $server failed" );
00203                     continue;
00204                 }
00205                 foreach ( $batchResult as $i => $value ) {
00206                     if ( $value !== false ) {
00207                         $result[$batchKeys[$i]] = $value;
00208                     }
00209                 }
00210             } catch ( RedisException $e ) {
00211                 $this->handleException( $server, $conn, $e );
00212             }
00213         }
00214 
00215         $this->debug( "getMulti for " . count( $keys ) . " keys " .
00216             "returned " . count( $result ) . " results" );
00217         wfProfileOut( __METHOD__ );
00218         return $result;
00219     }
00220 
00221     public function add( $key, $value, $expiry = 0 ) {
00222         wfProfileIn( __METHOD__ );
00223         list( $server, $conn ) = $this->getConnection( $key );
00224         if ( !$conn ) {
00225             wfProfileOut( __METHOD__ );
00226             return false;
00227         }
00228         $expiry = $this->convertToRelative( $expiry );
00229         try {
00230             $result = $conn->setnx( $key, $value );
00231             if ( $result && $expiry ) {
00232                 $conn->expire( $key, $expiry );
00233             }
00234         } catch ( RedisException $e ) {
00235             $result = false;
00236             $this->handleException( $server, $conn, $e );
00237         }
00238         $this->logRequest( 'add', $key, $server, $result );
00239         wfProfileOut( __METHOD__ );
00240         return $result;
00241     }
00242 
00247     public function replace( $key, $value, $expiry = 0 ) {
00248         wfProfileIn( __METHOD__ );
00249         list( $server, $conn ) = $this->getConnection( $key );
00250         if ( !$conn ) {
00251             wfProfileOut( __METHOD__ );
00252             return false;
00253         }
00254         if ( !$conn->exists( $key ) ) {
00255             wfProfileOut( __METHOD__ );
00256             return false;
00257         }
00258 
00259         $expiry = $this->convertToRelative( $expiry );
00260         try {
00261             if ( !$expiry ) {
00262                 $result = $conn->set( $key, $value );
00263             } else {
00264                 $result = $conn->setex( $key, $expiry, $value );
00265             }
00266         } catch ( RedisException $e ) {
00267             $result = false;
00268             $this->handleException( $server, $conn, $e );
00269         }
00270 
00271         $this->logRequest( 'replace', $key, $server, $result );
00272         wfProfileOut( __METHOD__ );
00273         return $result;
00274     }
00275 
00280     protected function getConnection( $key ) {
00281         if ( count( $this->servers ) === 1 ) {
00282             $candidates = $this->servers;
00283         } else {
00284             $candidates = $this->servers;
00285             ArrayUtils::consistentHashSort( $candidates, $key, '/' );
00286             if ( !$this->automaticFailover ) {
00287                 $candidates = array_slice( $candidates, 0, 1 );
00288             }
00289         }
00290 
00291         foreach ( $candidates as $server ) {
00292             $conn = $this->redisPool->getConnection( $server );
00293             if ( $conn ) {
00294                 return array( $server, $conn );
00295             }
00296         }
00297         return array( false, false );
00298     }
00299 
00303     protected function logError( $msg ) {
00304         wfDebugLog( 'redis', "Redis error: $msg\n" );
00305     }
00306 
00313     protected function handleException( $server, RedisConnRef $conn, $e ) {
00314         $this->redisPool->handleException( $server, $conn, $e );
00315     }
00316 
00320     public function logRequest( $method, $key, $server, $result ) {
00321         $this->debug( "$method $key on $server: " .
00322             ( $result === false ? "failure" : "success" ) );
00323     }
00324 }