72 parent::__construct( $params );
73 $redisConf = [
'serializer' =>
'none' ];
74 foreach ( [
'connectTimeout',
'persistent',
'password' ] as $opt ) {
75 if ( isset( $params[$opt] ) ) {
76 $redisConf[$opt] = $params[$opt];
81 $this->servers = $params[
'servers'];
82 foreach ( $this->servers as $key => $server ) {
83 $this->serverTagMap[is_int( $key ) ? $server : $key] = $server;
86 $this->automaticFailover = $params[
'automaticFailover'] ??
true;
92 protected function doGet( $key, $flags = 0, &$casToken =
null ) {
103 $blob = $conn->get( $key );
104 if ( $blob !==
false ) {
106 $valueSize = strlen( $blob );
111 if ( $getToken && $value !==
false ) {
114 }
catch ( RedisException $e ) {
120 $this->
logRequest(
'get', $key, $conn->getServer(), $e );
122 $this->
updateOpStats( self::METRIC_OP_GET, [ $key => [ 0, $valueSize ] ] );
127 protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
135 $valueSize = strlen( $serialized );
140 $result = $conn->setex( $key, $ttl, $serialized );
142 $result = $conn->set( $key, $serialized );
144 }
catch ( RedisException $e ) {
149 $this->
logRequest(
'set', $key, $conn->getServer(), $e );
151 $this->
updateOpStats( self::METRIC_OP_SET, [ $key => [ $valueSize, 0 ] ] );
165 $result = ( $conn->del( $key ) !== false );
166 }
catch ( RedisException $e ) {
171 $this->
logRequest(
'delete', $key, $conn->getServer(), $e );
182 foreach ( $keysByServer as $server => $batchKeys ) {
183 $conn = $connByServer[$server];
188 $conn->multi( Redis::PIPELINE );
189 foreach ( $batchKeys as $key ) {
192 $batchResult = $conn->exec();
193 if ( $batchResult ===
false ) {
194 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server,
true );
198 foreach ( $batchResult as $i => $blob ) {
199 if ( $blob !==
false ) {
200 $blobsFound[$batchKeys[$i]] = $blob;
203 }
catch ( RedisException $e ) {
207 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server, $e );
212 $valueSizesByKey = [];
213 foreach ( $keys as $key ) {
214 if ( array_key_exists( $key, $blobsFound ) ) {
215 $blob = $blobsFound[$key];
217 if ( $value !==
false ) {
218 $result[$key] = $value;
220 $valueSize = strlen( $blob );
224 $valueSizesByKey[$key] = [ 0, $valueSize ];
227 $this->
updateOpStats( self::METRIC_OP_GET, $valueSizesByKey );
232 protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
234 $op = $ttl ?
'setex' :
'set';
236 $keys = array_keys( $data );
237 $valueSizesByKey = [];
240 foreach ( $keysByServer as $server => $batchKeys ) {
241 $conn = $connByServer[$server];
246 $conn->multi( Redis::PIPELINE );
247 foreach ( $batchKeys as $key ) {
250 $conn->setex( $key, $ttl, $serialized );
252 $conn->set( $key, $serialized );
254 $valueSizesByKey[$key] = [ strlen( $serialized ), 0 ];
256 $batchResult = $conn->exec();
257 if ( $batchResult ===
false ) {
259 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
263 $result = $result && !in_array(
false, $batchResult,
true );
264 }
catch ( RedisException $e ) {
269 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
272 $this->
updateOpStats( self::METRIC_OP_SET, $valueSizesByKey );
279 foreach ( $keysByServer as $server => $batchKeys ) {
280 $conn = $connByServer[$server];
285 $conn->multi( Redis::PIPELINE );
286 foreach ( $batchKeys as $key ) {
289 $batchResult = $conn->exec();
290 if ( $batchResult ===
false ) {
292 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server,
true );
296 $result = $result && !in_array(
false, $batchResult,
true );
297 }
catch ( RedisException $e ) {
302 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server, $e );
305 $this->
updateOpStats( self::METRIC_OP_DELETE, array_values( $keys ) );
312 $op = ( $exptime == self::TTL_INDEFINITE )
314 : ( $relative ?
'expire' :
'expireAt' );
317 foreach ( $keysByServer as $server => $batchKeys ) {
318 $conn = $connByServer[$server];
322 $conn->multi( Redis::PIPELINE );
323 foreach ( $batchKeys as $key ) {
324 if ( $exptime == self::TTL_INDEFINITE ) {
325 $conn->persist( $key );
326 } elseif ( $relative ) {
332 $batchResult = $conn->exec();
333 if ( $batchResult ===
false ) {
335 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
338 $result = in_array(
false, $batchResult,
true ) ? false : $result;
339 }
catch ( RedisException $e ) {
344 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
347 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, array_values( $keys ) );
352 protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
360 $valueSize = strlen( $serialized );
363 $result = $conn->set(
366 $ttl ? [
'nx',
'ex' => $ttl ] : [
'nx' ]
368 }
catch ( RedisException $e ) {
373 $this->
logRequest(
'add', $key, $conn->getServer(), $result );
375 $this->
updateOpStats( self::METRIC_OP_ADD, [ $key => [ $valueSize, 0 ] ] );
392 local ttl, step, init = unpack( ARGV )
393 if redis.call(
'exists', key ) == 1 then
394 return redis.call(
'incrBy', key, step )
397 redis.call(
'setex', key, ttl, init )
399 redis.call(
'set', key, init )
403 $result = $conn->luaEval( $script, [ $key, $ttl, $step, $init ], 1 );
404 }
catch ( RedisException $e ) {
408 $this->
logRequest(
'incrWithInit', $key, $conn->getServer(), $result );
421 if ( $exptime == self::TTL_INDEFINITE ) {
422 $result = $conn->persist( $key );
423 $this->
logRequest(
'persist', $key, $conn->getServer(), $result );
424 } elseif ( $relative ) {
426 $this->
logRequest(
'expire', $key, $conn->getServer(), $result );
429 $this->
logRequest(
'expireAt', $key, $conn->getServer(), $result );
431 }
catch ( RedisException $e ) {
436 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, [ $key ] );
450 foreach ( $keys as $key ) {
451 $candidateTags = $this->getCandidateServerTagsForKey( $key );
455 while ( ( $tag = array_shift( $candidateTags ) ) !==
null ) {
456 $server = $this->serverTagMap[$tag];
458 if ( isset( $connByServer[$server] ) ) {
459 $conn = $connByServer[$server];
461 $conn = $this->redisPool->getConnection( $server, $this->logger );
470 if ( $this->automaticFailover && $candidateTags ) {
473 $info = $conn->info();
474 if ( ( $info[
'master_link_status'] ??
null ) ===
'down' ) {
481 }
catch ( RedisException $e ) {
483 $this->redisPool->handleError( $conn, $e );
488 $connByServer[$server] = $conn;
491 $keysByServer[$server][] = $key;
502 return [ $keysByServer, $connByServer,
$success ];
512 return reset( $connByServer ) ?:
null;
515 private function getCandidateServerTagsForKey(
string $key ): array {
516 $candidates = array_keys( $this->serverTagMap );
518 if ( count( $this->servers ) > 1 ) {
520 if ( !$this->automaticFailover ) {
521 $candidates = array_slice( $candidates, 0, 1 );
533 $this->logger->error(
"Redis error: $msg" );
546 $this->redisPool->handleError( $conn, $e );
556 public function logRequest( $op, $keys, $server, $e =
null ) {
557 $this->debug(
"$op($keys) on $server: " . ( $e ?
"failure" :
"success" ) );
static consistentHashSort(&$array, $key, $separator="\000")
Sort the given array in a pseudo-random order which depends only on the given key and each element va...
setLastError( $error)
Set the "last error" registry due to a problem encountered during an attempted operation.
Storage medium specific cache for storing items (e.g.
const PASS_BY_REF
Idiom for doGet() to return extra information by reference.
getExpirationAsTimestamp( $exptime)
Convert an optionally relative timestamp to an absolute time.
getSerialized( $value, $key)
Get the serialized form a value, logging a warning if it involves custom classes.
updateOpStats(string $op, array $keyInfo)
getExpirationAsTTL( $exptime)
Convert an optionally absolute expiry time to a relative time.
isRelativeExpiration( $exptime)
Redis-based caching module for redis server >= 2.6.12 and phpredis >= 2.2.4.
RedisConnectionPool $redisPool
handleException(RedisConnRef $conn, RedisException $e)
The redis extension throws an exception in response to various read, write and protocol errors.
array $servers
List of server names.
doSetMulti(array $data, $exptime=0, $flags=0)
doChangeTTL( $key, $exptime, $flags)
logRequest( $op, $keys, $server, $e=null)
Send information about a single request to the debug log.
doIncrWithInit( $key, $exptime, $step, $init, $flags)
doDelete( $key, $flags=0)
Delete an item.
getConnectionsForKeys(array $keys)
logError( $msg)
Log a fatal error.
__construct( $params)
Construct a RedisBagOStuff object.
doGet( $key, $flags=0, &$casToken=null)
Get an item.
array $serverTagMap
Map of (tag => server name)
doGetMulti(array $keys, $flags=0)
Get an associative array containing the item for each of the keys that have items.
doChangeTTLMulti(array $keys, $exptime, $flags=0)
doAdd( $key, $value, $exptime=0, $flags=0)
Insert an item if it does not already exist.
doSet( $key, $value, $exptime=0, $flags=0)
Set an item.
doDeleteMulti(array $keys, $flags=0)
Helper class to handle automatically marking connections as reusable (via RAII pattern)
static singleton(array $options)