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];
79 $this->redisPool = RedisConnectionPool::singleton( $redisConf );
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 ) {
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 (
$keys as $key ) {
185 $server = $conn->getServer();
186 $conns[$server] = $conn;
187 $batches[$server][] = $key;
192 foreach ( $batches as $server => $batchKeys ) {
193 $conn = $conns[$server];
198 $conn->multi( Redis::PIPELINE );
199 foreach ( $batchKeys as $key ) {
202 $batchResult = $conn->exec();
203 if ( $batchResult ===
false ) {
204 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server,
true );
208 foreach ( $batchResult as $i =>
$blob ) {
209 if (
$blob !==
false ) {
210 $blobsFound[$batchKeys[$i]] =
$blob;
213 }
catch ( RedisException $e ) {
217 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server, $e );
222 $valueSizesByKey = [];
223 foreach (
$keys as $key ) {
224 if ( array_key_exists( $key, $blobsFound ) ) {
225 $blob = $blobsFound[$key];
227 if ( $value !==
false ) {
228 $result[$key] = $value;
230 $valueSize = strlen(
$blob );
234 $valueSizesByKey[$key] = [ 0, $valueSize ];
237 $this->
updateOpStats( self::METRIC_OP_GET, $valueSizesByKey );
242 protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
248 foreach ( $data as $key => $value ) {
251 $server = $conn->getServer();
252 $conns[$server] = $conn;
253 $batches[$server][] = $key;
260 $op = $ttl ?
'setex' :
'set';
262 $valueSizesByKey = [];
263 foreach ( $batches as $server => $batchKeys ) {
264 $conn = $conns[$server];
269 $conn->multi( Redis::PIPELINE );
270 foreach ( $batchKeys as $key ) {
277 $valueSizesByKey[$key] = [ strlen(
$serialized ), 0 ];
279 $batchResult = $conn->exec();
280 if ( $batchResult ===
false ) {
282 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
286 $result = $result && !in_array(
false, $batchResult,
true );
287 }
catch ( RedisException $e ) {
292 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
295 $this->
updateOpStats( self::METRIC_OP_SET, $valueSizesByKey );
306 foreach (
$keys as $key ) {
309 $server = $conn->getServer();
310 $conns[$server] = $conn;
311 $batches[$server][] = $key;
317 foreach ( $batches as $server => $batchKeys ) {
318 $conn = $conns[$server];
323 $conn->multi( Redis::PIPELINE );
324 foreach ( $batchKeys as $key ) {
327 $batchResult = $conn->exec();
328 if ( $batchResult ===
false ) {
330 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server,
true );
334 $result = $result && !in_array(
false, $batchResult,
true );
335 }
catch ( RedisException $e ) {
340 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server, $e );
354 foreach (
$keys as $key ) {
357 $server = $conn->getServer();
358 $conns[$server] = $conn;
359 $batches[$server][] = $key;
366 $op = ( $exptime == self::TTL_INDEFINITE )
368 : ( $relative ?
'expire' :
'expireAt' );
370 foreach ( $batches as $server => $batchKeys ) {
371 $conn = $conns[$server];
375 $conn->multi( Redis::PIPELINE );
376 foreach ( $batchKeys as $key ) {
377 if ( $exptime == self::TTL_INDEFINITE ) {
378 $conn->persist( $key );
379 } elseif ( $relative ) {
385 $batchResult = $conn->exec();
386 if ( $batchResult ===
false ) {
388 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
391 $result = in_array(
false, $batchResult,
true ) ? false : $result;
392 }
catch ( RedisException $e ) {
397 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
405 protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
416 $result = $conn->set(
419 $ttl ? [
'nx',
'ex' => $ttl ] : [
'nx' ]
421 }
catch ( RedisException $e ) {
426 $this->
logRequest(
'add', $key, $conn->getServer(), $result );
428 $this->
updateOpStats( self::METRIC_OP_ADD, [ $key => [ $valueSize, 0 ] ] );
442 if ( $init === $step && $exptime == self::TTL_INDEFINITE ) {
443 $newValue = $conn->incrBy( $key, $step );
445 $conn->multi( Redis::PIPELINE );
448 (
string)( $init - $step ),
449 $ttl ? [
'nx',
'ex' => $ttl ] : [
'nx' ]
451 $conn->incrBy( $key, $step );
452 $batchResult = $conn->exec();
453 $newValue = ( $batchResult === false ) ?
false : $batchResult[1];
454 $this->
logRequest(
'incrWithInit', $key, $conn->getServer(), $newValue ===
false );
456 }
catch ( RedisException $e ) {
472 if ( $exptime == self::TTL_INDEFINITE ) {
473 $result = $conn->persist( $key );
474 $this->
logRequest(
'persist', $key, $conn->getServer(), $result );
475 } elseif ( $relative ) {
477 $this->
logRequest(
'expire', $key, $conn->getServer(), $result );
480 $this->
logRequest(
'expireAt', $key, $conn->getServer(), $result );
482 }
catch ( RedisException $e ) {
487 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, [ $key ] );
497 $candidates = array_keys( $this->serverTagMap );
499 if ( count( $this->servers ) > 1 ) {
500 ArrayUtils::consistentHashSort( $candidates, $key,
'/' );
501 if ( !$this->automaticFailover ) {
502 $candidates = array_slice( $candidates, 0, 1 );
506 while ( ( $tag = array_shift( $candidates ) ) !==
null ) {
507 $server = $this->serverTagMap[$tag];
508 $conn = $this->redisPool->getConnection( $server, $this->logger );
518 if ( $this->automaticFailover && $candidates ) {
521 $info = $conn->info();
522 if ( ( $info[
'master_link_status'] ??
null ) ===
'down' ) {
529 }
catch ( RedisException $e ) {
531 $this->redisPool->handleError( $conn, $e );
549 $this->logger->error(
"Redis error: $msg" );
562 $this->redisPool->handleError( $conn, $e );
573 $this->
debug(
"$op($keys) on $server: " . ( $e ?
"failure" :
"success" ) );
setLastError( $error)
Set the "last error" registry due to a problem encountered during an attempted operation.
genericKeyFromComponents(... $components)
At a minimum, there must be a keyspace and collection name component.
string $keyspace
Default keyspace; used by makeKey()
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.
convertGenericKey( $key)
Convert a "generic" reversible cache key into one for this cache.
array $servers
List of server names.
doSetMulti(array $data, $exptime=0, $flags=0)
makeKeyInternal( $keyspace, $components)
Make a cache key for the given keyspace and components.
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.
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)
Helper class to manage Redis connections.
foreach( $res as $row) $serialized