mirror of
https://github.com/predis/predis.git
synced 2026-08-30 20:21:31 +00:00
Handle -READONLY as a redirection signal for Redis Cluster (AWS ElastiCache support) (#1656)
* feature: Added support for READONLY responses from AWS Elastic Cache * chore: improve -READONLY handler comment and add changelog entry (#1656) * tests: add unit test demonstrating ServerException behavior with READONLY error
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Handle `-READONLY` responses in Redis Cluster for AWS ElastiCache Redis OSS failover events
|
||||
### Changed
|
||||
- Include command name in unsupported container command error messages (#1653)
|
||||
### Fixed
|
||||
|
||||
@@ -465,6 +465,9 @@ class RedisCluster extends AbstractAggregateConnection implements ClusterInterfa
|
||||
$details = explode(' ', $error->getMessage(), 2);
|
||||
|
||||
switch ($details[0]) {
|
||||
case 'READONLY':
|
||||
return $this->onReadOnlyResponse($command);
|
||||
|
||||
case 'MOVED':
|
||||
return $this->onMovedResponse($command, $details[1]);
|
||||
|
||||
@@ -476,6 +479,30 @@ class RedisCluster extends AbstractAggregateConnection implements ClusterInterfa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles -READONLY responses by disconnecting the current node's connection
|
||||
* and refreshing the slots map (when cluster slots are enabled), then
|
||||
* re-executing the command so it is routed to the updated primary node.
|
||||
*
|
||||
* This is a workaround for AWS ElastiCache Redis OSS, which may return
|
||||
* -READONLY errors during failover events. Standard Redis clusters do not
|
||||
* exhibit this behavior.
|
||||
*
|
||||
* @param CommandInterface $command Command that generated the -READONLY response.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function onReadOnlyResponse(CommandInterface $command)
|
||||
{
|
||||
if ($this->useClusterSlots) {
|
||||
$connection = $this->getConnectionByCommand($command);
|
||||
$connection->disconnect();
|
||||
$this->askSlotMap();
|
||||
}
|
||||
|
||||
return $this->executeCommand($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles -MOVED responses by executing again the command against the node
|
||||
* indicated by the Redis response.
|
||||
|
||||
@@ -738,6 +738,45 @@ class RedisClusterTest extends PredisTestCase
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
*/
|
||||
public function testRetriesExecutingCommandOnReadonlyServerExceptionDoesNotUpdateSlotMap(): void
|
||||
{
|
||||
$serverException = new Response\ServerException('READONLY You can\'t write against a read only replica.');
|
||||
$command = $this->getCommandFactory()->create('get', ['node:1001']);
|
||||
|
||||
$connection1 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=0-5460');
|
||||
$connection1
|
||||
->expects($this->exactly(2))
|
||||
->method('executeCommand')
|
||||
->with($this->isRedisCommand($command))
|
||||
->willThrowException($serverException);
|
||||
|
||||
$connection1
|
||||
->expects($this->never())
|
||||
->method('disconnect');
|
||||
|
||||
/** @var FactoryInterface|MockObject */
|
||||
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
|
||||
$factory
|
||||
->expects($this->never())
|
||||
->method('create');
|
||||
|
||||
$cluster = new RedisCluster($factory, new Parameters());
|
||||
$cluster->useClusterSlots(true);
|
||||
$cluster->setRetryInterval(0);
|
||||
$cluster->setRetryLimit(1);
|
||||
|
||||
$cluster->add($connection1);
|
||||
$cluster->getSlotMap()->setSlots(0, 5460, '127.0.0.1:6381');
|
||||
|
||||
$this->expectException('Predis\Response\ServerException');
|
||||
$this->expectExceptionMessage('READONLY You can\'t write against a read only replica.');
|
||||
|
||||
$cluster->executeCommand($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group disconnected
|
||||
* @group slow
|
||||
|
||||
Reference in New Issue
Block a user