85 parent::__construct( $params );
86 $redisConf = [
'serializer' =>
'none' ];
87 foreach ( [
'connectTimeout',
'persistent',
'password',
'prefix' ] as $opt ) {
88 if ( isset( $params[$opt] ) ) {
89 $redisConf[$opt] = $params[$opt];
94 $this->servers = $params[
'servers'];
95 foreach ( $this->servers as $key => $server ) {
96 $this->serverTagMap[is_int( $key ) ? $server : $key] = $server;
99 $this->automaticFailover = $params[
'automaticFailover'] ??
true;
105 protected function doGet( $key, $flags = 0, &$casToken =
null ) {
116 $blob = $conn->get( $key );
117 if ( $blob !==
false ) {
119 $valueSize = strlen( $blob );
124 if ( $getToken && $value !==
false ) {
127 }
catch ( RedisException $e ) {
133 $this->
logRequest(
'get', $key, $conn->getServer(), $e );
135 $this->
updateOpStats( self::METRIC_OP_GET, [ $key => [ 0, $valueSize ] ] );
140 protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
148 $valueSize = strlen( $serialized );
153 $result = $conn->setex( $key, $ttl, $serialized );
155 $result = $conn->set( $key, $serialized );
157 }
catch ( RedisException $e ) {
162 $this->
logRequest(
'set', $key, $conn->getServer(), $e );
164 $this->
updateOpStats( self::METRIC_OP_SET, [ $key => [ $valueSize, 0 ] ] );
178 $result = ( $conn->del( $key ) !== false );
179 }
catch ( RedisException $e ) {
184 $this->
logRequest(
'delete', $key, $conn->getServer(), $e );
195 foreach ( $keysByServer as $server => $batchKeys ) {
196 $conn = $connByServer[$server];
201 $conn->multi( Redis::PIPELINE );
202 foreach ( $batchKeys as $key ) {
205 $batchResult = $conn->exec();
206 if ( $batchResult ===
false ) {
207 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server,
true );
211 foreach ( $batchResult as $i => $blob ) {
212 if ( $blob !==
false ) {
213 $blobsFound[$batchKeys[$i]] = $blob;
216 }
catch ( RedisException $e ) {
220 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server, $e );
225 $valueSizesByKey = [];
226 foreach ( $keys as $key ) {
227 if ( array_key_exists( $key, $blobsFound ) ) {
228 $blob = $blobsFound[$key];
230 if ( $value !==
false ) {
231 $result[$key] = $value;
233 $valueSize = strlen( $blob );
237 $valueSizesByKey[$key] = [ 0, $valueSize ];
240 $this->
updateOpStats( self::METRIC_OP_GET, $valueSizesByKey );
245 protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
247 $op = $ttl ?
'setex' :
'set';
249 $keys = array_keys( $data );
250 $valueSizesByKey = [];
253 foreach ( $keysByServer as $server => $batchKeys ) {
254 $conn = $connByServer[$server];
259 $conn->multi( Redis::PIPELINE );
260 foreach ( $batchKeys as $key ) {
263 $conn->setex( $key, $ttl, $serialized );
265 $conn->set( $key, $serialized );
267 $valueSizesByKey[$key] = [ strlen( $serialized ), 0 ];
269 $batchResult = $conn->exec();
270 if ( $batchResult ===
false ) {
272 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
276 $result = $result && !in_array(
false, $batchResult,
true );
277 }
catch ( RedisException $e ) {
282 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
285 $this->
updateOpStats( self::METRIC_OP_SET, $valueSizesByKey );
292 foreach ( $keysByServer as $server => $batchKeys ) {
293 $conn = $connByServer[$server];
298 $conn->multi( Redis::PIPELINE );
299 foreach ( $batchKeys as $key ) {
302 $batchResult = $conn->exec();
303 if ( $batchResult ===
false ) {
305 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server,
true );
309 $result = $result && !in_array(
false, $batchResult,
true );
310 }
catch ( RedisException $e ) {
315 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server, $e );
318 $this->
updateOpStats( self::METRIC_OP_DELETE, array_values( $keys ) );
325 $op = ( $exptime == self::TTL_INDEFINITE )
327 : ( $relative ?
'expire' :
'expireAt' );
330 foreach ( $keysByServer as $server => $batchKeys ) {
331 $conn = $connByServer[$server];
335 $conn->multi( Redis::PIPELINE );
336 foreach ( $batchKeys as $key ) {
337 if ( $exptime == self::TTL_INDEFINITE ) {
338 $conn->persist( $key );
339 } elseif ( $relative ) {
345 $batchResult = $conn->exec();
346 if ( $batchResult ===
false ) {
348 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
351 $result = in_array(
false, $batchResult,
true ) ? false : $result;
352 }
catch ( RedisException $e ) {
357 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
360 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, array_values( $keys ) );
365 protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
373 $valueSize = strlen( $serialized );
376 $result = $conn->set(
379 $ttl ? [
'nx',
'ex' => $ttl ] : [
'nx' ]
381 }
catch ( RedisException $e ) {
386 $this->
logRequest(
'add', $key, $conn->getServer(), $result );
388 $this->
updateOpStats( self::METRIC_OP_ADD, [ $key => [ $valueSize, 0 ] ] );
405 local ttl, step, init = unpack( ARGV )
406 if redis.call(
'exists', key ) == 1 then
407 return redis.call(
'incrBy', key, step )
410 redis.call(
'setex', key, ttl, init )
412 redis.call(
'set', key, init )
416 $result = $conn->luaEval( $script, [ $key, $ttl, $step, $init ], 1 );
417 }
catch ( RedisException $e ) {
421 $this->
logRequest(
'incrWithInit', $key, $conn->getServer(), $result );
434 if ( $exptime == self::TTL_INDEFINITE ) {
435 $result = $conn->persist( $key );
436 $this->
logRequest(
'persist', $key, $conn->getServer(), $result );
437 } elseif ( $relative ) {
439 $this->
logRequest(
'expire', $key, $conn->getServer(), $result );
442 $this->
logRequest(
'expireAt', $key, $conn->getServer(), $result );
444 }
catch ( RedisException $e ) {
449 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, [ $key ] );
464 foreach ( $keys as $key ) {
465 $candidateTags = $this->getCandidateServerTagsForKey( $key );
470 while ( ( $tag = array_shift( $candidateTags ) ) !==
null ) {
471 $server = $this->serverTagMap[$tag];
473 if ( isset( $connByServer[$server] ) ) {
474 $conn = $connByServer[$server];
476 $conn = $this->redisPool->getConnection( $server, $this->logger );
485 if ( $this->automaticFailover && $candidateTags ) {
488 $info = $conn->info();
489 if ( ( $info[
'master_link_status'] ??
null ) ===
'down' ) {
496 }
catch ( RedisException $e ) {
498 $this->redisPool->handleError( $conn, $e );
503 $connByServer[$server] = $conn;
506 $keysByServer[$server][] = $key;
517 return [ $keysByServer, $connByServer,
$success ];
528 return reset( $connByServer ) ?:
null;
531 private function getCandidateServerTagsForKey(
string $key ): array {
532 $candidates = array_keys( $this->serverTagMap );
534 if ( count( $this->servers ) > 1 ) {
535 ArrayUtils::consistentHashSort( $candidates, $key,
'/' );
536 if ( !$this->automaticFailover ) {
537 $candidates = array_slice( $candidates, 0, 1 );
550 $this->logger->error(
"Redis error: $msg" );
564 $this->redisPool->handleError( $conn, $e );
575 public function logRequest( $op, $keys, $server, $e =
null ) {
576 $this->debug(
"$op($keys) on $server: " . ( $e ?
"failure" :
"success" ) );
581class_alias( RedisBagOStuff::class,
'RedisBagOStuff' );
A collection of static methods to play with arrays.