MediaWiki
REL1_21
|
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 }