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