73 parent::__construct( $params );
74 $redisConf = [
'serializer' =>
'none' ];
75 foreach ( [
'connectTimeout',
'persistent',
'password',
'prefix' ] as $opt ) {
76 if ( isset( $params[$opt] ) ) {
77 $redisConf[$opt] = $params[$opt];
82 $this->servers = $params[
'servers'];
83 foreach ( $this->servers as $key => $server ) {
84 $this->serverTagMap[is_int( $key ) ? $server : $key] = $server;
87 $this->automaticFailover = $params[
'automaticFailover'] ??
true;
94 protected function doGet( $key, $flags = 0, &$casToken =
null ) {
105 $blob = $conn->get( $key );
106 if ( $blob !==
false ) {
108 $valueSize = strlen( $blob );
113 if ( $getToken && $value !==
false ) {
116 }
catch ( RedisException $e ) {
122 $this->
logRequest(
'get', $key, $conn->getServer(), $e );
124 $this->
updateOpStats( self::METRIC_OP_GET, [ $key => [ 0, $valueSize ] ] );
130 protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
138 $valueSize = strlen( $serialized );
143 $result = $conn->setex( $key, $ttl, $serialized );
145 $result = $conn->set( $key, $serialized );
147 }
catch ( RedisException $e ) {
152 $this->
logRequest(
'set', $key, $conn->getServer(), $e );
154 $this->
updateOpStats( self::METRIC_OP_SET, [ $key => [ $valueSize, 0 ] ] );
169 $result = ( $conn->del( $key ) !== false );
170 }
catch ( RedisException $e ) {
175 $this->
logRequest(
'delete', $key, $conn->getServer(), $e );
187 foreach ( $keysByServer as $server => $batchKeys ) {
188 $conn = $connByServer[$server];
193 $conn->multi( Redis::PIPELINE );
194 foreach ( $batchKeys as $key ) {
197 $batchResult = $conn->exec();
198 if ( $batchResult ===
false ) {
199 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server,
true );
203 foreach ( $batchResult as $i => $blob ) {
204 if ( $blob !==
false ) {
205 $blobsFound[$batchKeys[$i]] = $blob;
208 }
catch ( RedisException $e ) {
212 $this->
logRequest(
'get', implode(
',', $batchKeys ), $server, $e );
217 $valueSizesByKey = [];
218 foreach ( $keys as $key ) {
219 if ( array_key_exists( $key, $blobsFound ) ) {
220 $blob = $blobsFound[$key];
222 if ( $value !==
false ) {
223 $result[$key] = $value;
225 $valueSize = strlen( $blob );
229 $valueSizesByKey[$key] = [ 0, $valueSize ];
232 $this->
updateOpStats( self::METRIC_OP_GET, $valueSizesByKey );
238 protected function doSetMulti( array $data, $exptime = 0, $flags = 0 ) {
240 $op = $ttl ?
'setex' :
'set';
242 $keys = array_keys( $data );
243 $valueSizesByKey = [];
246 foreach ( $keysByServer as $server => $batchKeys ) {
247 $conn = $connByServer[$server];
252 $conn->multi( Redis::PIPELINE );
253 foreach ( $batchKeys as $key ) {
256 $conn->setex( $key, $ttl, $serialized );
258 $conn->set( $key, $serialized );
260 $valueSizesByKey[$key] = [ strlen( $serialized ), 0 ];
262 $batchResult = $conn->exec();
263 if ( $batchResult ===
false ) {
265 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
269 $result = $result && !in_array(
false, $batchResult,
true );
270 }
catch ( RedisException $e ) {
275 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
278 $this->
updateOpStats( self::METRIC_OP_SET, $valueSizesByKey );
286 foreach ( $keysByServer as $server => $batchKeys ) {
287 $conn = $connByServer[$server];
292 $conn->multi( Redis::PIPELINE );
293 foreach ( $batchKeys as $key ) {
296 $batchResult = $conn->exec();
297 if ( $batchResult ===
false ) {
299 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server,
true );
303 $result = $result && !in_array(
false, $batchResult,
true );
304 }
catch ( RedisException $e ) {
309 $this->
logRequest(
'delete', implode(
',', $batchKeys ), $server, $e );
312 $this->
updateOpStats( self::METRIC_OP_DELETE, array_values( $keys ) );
320 $op = ( $exptime == self::TTL_INDEFINITE )
322 : ( $relative ?
'expire' :
'expireAt' );
325 foreach ( $keysByServer as $server => $batchKeys ) {
326 $conn = $connByServer[$server];
330 $conn->multi( Redis::PIPELINE );
331 foreach ( $batchKeys as $key ) {
332 if ( $exptime == self::TTL_INDEFINITE ) {
333 $conn->persist( $key );
334 } elseif ( $relative ) {
340 $batchResult = $conn->exec();
341 if ( $batchResult ===
false ) {
343 $this->
logRequest( $op, implode(
',', $batchKeys ), $server,
true );
346 $result = in_array(
false, $batchResult,
true ) ? false : $result;
347 }
catch ( RedisException $e ) {
352 $this->
logRequest( $op, implode(
',', $batchKeys ), $server, $e );
355 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, array_values( $keys ) );
361 protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
369 $valueSize = strlen( $serialized );
372 $result = $conn->set(
375 $ttl ? [
'nx',
'ex' => $ttl ] : [
'nx' ]
377 }
catch ( RedisException $e ) {
382 $this->
logRequest(
'add', $key, $conn->getServer(), $result );
384 $this->
updateOpStats( self::METRIC_OP_ADD, [ $key => [ $valueSize, 0 ] ] );
402 local ttl, step, init = unpack( ARGV )
403 if redis.call(
'exists', key ) == 1 then
404 return redis.call(
'incrBy', key, step )
407 redis.call(
'setex', key, ttl, init )
409 redis.call(
'set', key, init )
413 $result = $conn->luaEval( $script, [ $key, $ttl, $step, $init ], 1 );
414 }
catch ( RedisException $e ) {
418 $this->
logRequest(
'incrWithInit', $key, $conn->getServer(), $result );
432 if ( $exptime == self::TTL_INDEFINITE ) {
433 $result = $conn->persist( $key );
434 $this->
logRequest(
'persist', $key, $conn->getServer(), $result );
435 } elseif ( $relative ) {
437 $this->
logRequest(
'expire', $key, $conn->getServer(), $result );
440 $this->
logRequest(
'expireAt', $key, $conn->getServer(), $result );
442 }
catch ( RedisException $e ) {
447 $this->
updateOpStats( self::METRIC_OP_CHANGE_TTL, [ $key ] );
462 foreach ( $keys as $key ) {
463 $candidateTags = $this->getCandidateServerTagsForKey( $key );
468 while ( ( $tag = array_shift( $candidateTags ) ) !==
null ) {
469 $server = $this->serverTagMap[$tag];
471 if ( isset( $connByServer[$server] ) ) {
472 $conn = $connByServer[$server];
474 $conn = $this->redisPool->getConnection( $server, $this->logger );
483 if ( $this->automaticFailover && $candidateTags ) {
486 $info = $conn->info();
487 if ( ( $info[
'master_link_status'] ??
null ) ===
'down' ) {
494 }
catch ( RedisException $e ) {
496 $this->redisPool->handleError( $conn, $e );
501 $connByServer[$server] = $conn;
504 $keysByServer[$server][] = $key;
515 return [ $keysByServer, $connByServer,
$success ];
526 return reset( $connByServer ) ?:
null;
529 private function getCandidateServerTagsForKey(
string $key ): array {
530 $candidates = array_keys( $this->serverTagMap );
532 if ( count( $this->servers ) > 1 ) {
533 ArrayUtils::consistentHashSort( $candidates, $key,
'/' );
534 if ( !$this->automaticFailover ) {
535 $candidates = array_slice( $candidates, 0, 1 );
548 $this->logger->error(
"Redis error: $msg" );
562 $this->redisPool->handleError( $conn, $e );
573 public function logRequest( $op, $keys, $server, $e =
null ) {
574 $this->debug(
"$op($keys) on $server: " . ( $e ?
"failure" :
"success" ) );
579class_alias( RedisBagOStuff::class,
'RedisBagOStuff' );