feature #24887 [Cache][Lock] Add RedisProxy for lazy Redis connections (nicolas-grekas)
This PR was merged into the 3.4 branch.
Discussion
----------
[Cache][Lock] Add RedisProxy for lazy Redis connections
| Q | A
| ------------- | ---
| Branch? | 3.4
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #24865
| License | MIT
| Doc PR | -
That's the only provider that is not lazy by default, leading to bad DX (see linked issue.)
Best reviewed [ignoring whitespaces](https://github.com/symfony/symfony/pull/24887/files?w=1).
Commits
-------
1f5e3538d8
[Cache][Lock] Add RedisProxy for lazy Redis connections
This commit is contained in:
commit
bf5203102a
@ -134,7 +134,7 @@ class CachePoolPass implements CompilerPassInterface
|
|||||||
$definition = new Definition(AbstractAdapter::class);
|
$definition = new Definition(AbstractAdapter::class);
|
||||||
$definition->setPublic(false);
|
$definition->setPublic(false);
|
||||||
$definition->setFactory(array(AbstractAdapter::class, 'createConnection'));
|
$definition->setFactory(array(AbstractAdapter::class, 'createConnection'));
|
||||||
$definition->setArguments(array($dsn));
|
$definition->setArguments(array($dsn, array('lazy' => true)));
|
||||||
$container->setDefinition($name, $definition);
|
$container->setDefinition($name, $definition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1547,7 +1547,7 @@ class FrameworkExtension extends Extension
|
|||||||
$connectionDefinition = new Definition(\stdClass::class);
|
$connectionDefinition = new Definition(\stdClass::class);
|
||||||
$connectionDefinition->setPublic(false);
|
$connectionDefinition->setPublic(false);
|
||||||
$connectionDefinition->setFactory(array(AbstractAdapter::class, 'createConnection'));
|
$connectionDefinition->setFactory(array(AbstractAdapter::class, 'createConnection'));
|
||||||
$connectionDefinition->setArguments(array($storeDsn));
|
$connectionDefinition->setArguments(array($storeDsn, array('lazy' => true)));
|
||||||
$container->setDefinition($connectionDefinitionId, $connectionDefinition);
|
$container->setDefinition($connectionDefinitionId, $connectionDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ class PredisAdapterTest extends AbstractRedisAdapterTest
|
|||||||
'persistent_id' => null,
|
'persistent_id' => null,
|
||||||
'read_timeout' => 0,
|
'read_timeout' => 0,
|
||||||
'retry_interval' => 0,
|
'retry_interval' => 0,
|
||||||
|
'lazy' => false,
|
||||||
'database' => '1',
|
'database' => '1',
|
||||||
'password' => null,
|
'password' => null,
|
||||||
);
|
);
|
||||||
|
@ -13,13 +13,22 @@ namespace Symfony\Component\Cache\Tests\Adapter;
|
|||||||
|
|
||||||
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||||
|
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||||
|
|
||||||
class RedisAdapterTest extends AbstractRedisAdapterTest
|
class RedisAdapterTest extends AbstractRedisAdapterTest
|
||||||
{
|
{
|
||||||
public static function setupBeforeClass()
|
public static function setupBeforeClass()
|
||||||
{
|
{
|
||||||
parent::setupBeforeClass();
|
parent::setupBeforeClass();
|
||||||
self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'));
|
self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), array('lazy' => true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createCachePool($defaultLifetime = 0)
|
||||||
|
{
|
||||||
|
$adapter = parent::createCachePool($defaultLifetime);
|
||||||
|
$this->assertInstanceOf(RedisProxy::class, self::$redis);
|
||||||
|
|
||||||
|
return $adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateConnection()
|
public function testCreateConnection()
|
||||||
|
65
src/Symfony/Component/Cache/Traits/RedisProxy.php
Normal file
65
src/Symfony/Component/Cache/Traits/RedisProxy.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Cache\Traits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class RedisProxy
|
||||||
|
{
|
||||||
|
private $redis;
|
||||||
|
private $initializer;
|
||||||
|
private $ready = false;
|
||||||
|
|
||||||
|
public function __construct(\Redis $redis, \Closure $initializer)
|
||||||
|
{
|
||||||
|
$this->redis = $redis;
|
||||||
|
$this->initializer = $initializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call($method, array $args)
|
||||||
|
{
|
||||||
|
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
|
||||||
|
|
||||||
|
return \call_user_func_array(array($this->redis, $method), $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
|
||||||
|
{
|
||||||
|
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
|
||||||
|
|
||||||
|
return $this->redis->hscan($strKey, $iIterator, $strPattern, $iCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scan(&$iIterator, $strPattern = null, $iCount = null)
|
||||||
|
{
|
||||||
|
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
|
||||||
|
|
||||||
|
return $this->redis->scan($iIterator, $strPattern, $iCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
|
||||||
|
{
|
||||||
|
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
|
||||||
|
|
||||||
|
return $this->redis->sscan($strKey, $iIterator, $strPattern, $iCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function zscan($strKey, &$iIterator, $strPattern = null, $iCount = null)
|
||||||
|
{
|
||||||
|
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
|
||||||
|
|
||||||
|
return $this->redis->zscan($strKey, $iIterator, $strPattern, $iCount);
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ trait RedisTrait
|
|||||||
'timeout' => 30,
|
'timeout' => 30,
|
||||||
'read_timeout' => 0,
|
'read_timeout' => 0,
|
||||||
'retry_interval' => 0,
|
'retry_interval' => 0,
|
||||||
|
'lazy' => false,
|
||||||
);
|
);
|
||||||
private $redis;
|
private $redis;
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ trait RedisTrait
|
|||||||
}
|
}
|
||||||
if ($redisClient instanceof \RedisCluster) {
|
if ($redisClient instanceof \RedisCluster) {
|
||||||
$this->enableVersioning();
|
$this->enableVersioning();
|
||||||
} elseif (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \Predis\Client) {
|
} elseif (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
|
||||||
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
||||||
}
|
}
|
||||||
$this->redis = $redisClient;
|
$this->redis = $redisClient;
|
||||||
@ -117,19 +118,30 @@ trait RedisTrait
|
|||||||
if (is_a($class, \Redis::class, true)) {
|
if (is_a($class, \Redis::class, true)) {
|
||||||
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
|
$connect = $params['persistent'] || $params['persistent_id'] ? 'pconnect' : 'connect';
|
||||||
$redis = new $class();
|
$redis = new $class();
|
||||||
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], $params['persistent_id'], $params['retry_interval']);
|
|
||||||
|
|
||||||
if (@!$redis->isConnected()) {
|
$initializer = function ($redis) use ($connect, $params, $dsn, $auth) {
|
||||||
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
|
@$redis->{$connect}($params['host'], $params['port'], $params['timeout'], $params['persistent_id'], $params['retry_interval']);
|
||||||
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((null !== $auth && !$redis->auth($auth))
|
if (@!$redis->isConnected()) {
|
||||||
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
|
$e = ($e = error_get_last()) && preg_match('/^Redis::p?connect\(\): (.*)/', $e['message'], $e) ? sprintf(' (%s)', $e[1]) : '';
|
||||||
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
|
throw new InvalidArgumentException(sprintf('Redis connection failed%s: %s', $e, $dsn));
|
||||||
) {
|
}
|
||||||
$e = preg_replace('/^ERR /', '', $redis->getLastError());
|
|
||||||
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
|
if ((null !== $auth && !$redis->auth($auth))
|
||||||
|
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
|
||||||
|
|| ($params['read_timeout'] && !$redis->setOption(\Redis::OPT_READ_TIMEOUT, $params['read_timeout']))
|
||||||
|
) {
|
||||||
|
$e = preg_replace('/^ERR /', '', $redis->getLastError());
|
||||||
|
throw new InvalidArgumentException(sprintf('Redis connection failed (%s): %s', $e, $dsn));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($params['lazy']) {
|
||||||
|
$redis = new RedisProxy($redis, $initializer);
|
||||||
|
} else {
|
||||||
|
$initializer($redis);
|
||||||
}
|
}
|
||||||
} elseif (is_a($class, \Predis\Client::class, true)) {
|
} elseif (is_a($class, \Predis\Client::class, true)) {
|
||||||
$params['scheme'] = $scheme;
|
$params['scheme'] = $scheme;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock\Store;
|
namespace Symfony\Component\Lock\Store;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\Lock\Exception\LockConflictedException;
|
use Symfony\Component\Lock\Exception\LockConflictedException;
|
||||||
use Symfony\Component\Lock\Exception\LockExpiredException;
|
use Symfony\Component\Lock\Exception\LockExpiredException;
|
||||||
@ -24,14 +25,6 @@ use Symfony\Component\Lock\StoreInterface;
|
|||||||
*/
|
*/
|
||||||
class RedisStore implements StoreInterface
|
class RedisStore implements StoreInterface
|
||||||
{
|
{
|
||||||
private static $defaultConnectionOptions = array(
|
|
||||||
'class' => null,
|
|
||||||
'persistent' => 0,
|
|
||||||
'persistent_id' => null,
|
|
||||||
'timeout' => 30,
|
|
||||||
'read_timeout' => 0,
|
|
||||||
'retry_interval' => 0,
|
|
||||||
);
|
|
||||||
private $redis;
|
private $redis;
|
||||||
private $initialTtl;
|
private $initialTtl;
|
||||||
|
|
||||||
@ -41,7 +34,7 @@ class RedisStore implements StoreInterface
|
|||||||
*/
|
*/
|
||||||
public function __construct($redisClient, $initialTtl = 300.0)
|
public function __construct($redisClient, $initialTtl = 300.0)
|
||||||
{
|
{
|
||||||
if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client) {
|
if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client && !$redisClient instanceof RedisProxy) {
|
||||||
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +132,7 @@ class RedisStore implements StoreInterface
|
|||||||
*/
|
*/
|
||||||
private function evaluate($script, $resource, array $args)
|
private function evaluate($script, $resource, array $args)
|
||||||
{
|
{
|
||||||
if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster) {
|
if ($this->redis instanceof \Redis || $this->redis instanceof \RedisCluster || $this->redis instanceof RedisProxy) {
|
||||||
return $this->redis->eval($script, array_merge(array($resource), $args), 1);
|
return $this->redis->eval($script, array_merge(array($resource), $args), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Lock\Store;
|
namespace Symfony\Component\Lock\Store;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Traits\RedisProxy;
|
||||||
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
use Symfony\Component\Lock\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +28,7 @@ class StoreFactory
|
|||||||
*/
|
*/
|
||||||
public static function createStore($connection)
|
public static function createStore($connection)
|
||||||
{
|
{
|
||||||
if ($connection instanceof \Redis || $connection instanceof \RedisArray || $connection instanceof \RedisCluster || $connection instanceof \Predis\Client) {
|
if ($connection instanceof \Redis || $connection instanceof \RedisArray || $connection instanceof \RedisCluster || $connection instanceof \Predis\Client || $connection instanceof RedisProxy) {
|
||||||
return new RedisStore($connection);
|
return new RedisStore($connection);
|
||||||
}
|
}
|
||||||
if ($connection instanceof \Memcached) {
|
if ($connection instanceof \Memcached) {
|
||||||
|
Reference in New Issue
Block a user