Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit899f52e

Browse files
[Cache] added support for connecting to Redis clusters via DSN
1 parent38655bd commit899f52e

File tree

9 files changed

+190
-59
lines changed

9 files changed

+190
-59
lines changed

‎composer.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@
9999
"doctrine/doctrine-bundle":"~1.4",
100100
"monolog/monolog":"~1.11",
101101
"ocramius/proxy-manager":"~0.4|~1.0|~2.0",
102-
"predis/predis":"~1.0",
102+
"predis/predis":"~1.1",
103103
"egulias/email-validator":"~1.2,>=1.2.8|~2.0",
104104
"symfony/phpunit-bridge":"~3.4|~4.0",
105105
"symfony/security-acl":"~2.8|~3.0",

‎src/Symfony/Component/Cache/Adapter/AbstractAdapter.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public static function createConnection($dsn, array $options = array())
148148
if (!\is_string($dsn)) {
149149
thrownewInvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.',__METHOD__,\gettype($dsn)));
150150
}
151-
if (0 ===strpos($dsn,'redis://')) {
151+
if (0 ===strpos($dsn,'redis:')) {
152152
return RedisAdapter::createConnection($dsn,$options);
153153
}
154154
if (0 ===strpos($dsn,'memcached:')) {

‎src/Symfony/Component/Cache/CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ CHANGELOG
44
4.2.0
55
-----
66

7-
* added support for configuring multiple Memcached servers in one DSN
7+
* added support for connecting to Redis clusters via DSN
8+
* added support for configuring multiple Memcached servers via DSN
89
* added`MarshallerInterface` and`DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
910
* added`CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
1011
* added sub-second expiry accuracy for backends that support it

‎src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterTest.php‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,13 @@ public function testCreateConnection()
3434

3535
$params =array(
3636
'scheme' =>'tcp',
37-
'host' =>$redisHost,
38-
'path' =>'',
39-
'dbindex' =>'1',
37+
'host' =>'localhost',
4038
'port' =>6379,
41-
'class' =>'Predis\Client',
42-
'timeout' =>3,
4339
'persistent' =>0,
44-
'persistent_id' =>null,
45-
'read_timeout' =>0,
46-
'retry_interval' =>0,
47-
'compression' =>true,
48-
'tcp_keepalive' =>0,
49-
'lazy' =>false,
40+
'timeout' =>3,
41+
'read_write_timeout' =>0,
42+
'tcp_nodelay' =>true,
5043
'database' =>'1',
51-
'password' =>null,
5244
);
5345
$this->assertSame($params,$connection->getParameters()->toArray());
5446
}

‎src/Symfony/Component/Cache/Tests/Adapter/PredisRedisClusterAdapterTest.php‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111

1212
namespaceSymfony\Component\Cache\Tests\Adapter;
1313

14+
useSymfony\Component\Cache\Adapter\RedisAdapter;
15+
1416
class PredisRedisClusterAdapterTestextends AbstractRedisAdapterTest
1517
{
1618
publicstaticfunctionsetupBeforeClass()
1719
{
1820
if (!$hosts =getenv('REDIS_CLUSTER_HOSTS')) {
1921
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
2022
}
21-
self::$redis =new \Predis\Client(explode('',$hosts),array('cluster' =>'redis'));
23+
24+
self::$redis = RedisAdapter::createConnection('redis:?host['.str_replace('',']&host[',$hosts).']',array('class' => \Predis\Client::class,'redis_cluster' =>true));
2225
}
2326

2427
publicstaticfunctiontearDownAfterClass()

‎src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public function createCachePool($defaultLifetime = 0)
3333

3434
publicfunctiontestCreateConnection()
3535
{
36+
$redis = RedisAdapter::createConnection('redis:?host[h1]&host[h2]&host[/foo:]');
37+
$this->assertInstanceOf(\RedisArray::class,$redis);
38+
$this->assertSame(array('h1:6379','h2:6379','/foo'),$redis->_hosts());
39+
@unset($redis);// some versions of phpredis connect on destruct, let's silence the warning
40+
3641
$redisHost =getenv('REDIS_HOST');
3742

3843
$redis = RedisAdapter::createConnection('redis://'.$redisHost);

‎src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
namespaceSymfony\Component\Cache\Tests\Adapter;
1313

14+
useSymfony\Component\Cache\Adapter\AbstractAdapter;
15+
useSymfony\Component\Cache\Adapter\RedisAdapter;
16+
useSymfony\Component\Cache\Traits\RedisClusterProxy;
17+
1418
class RedisClusterAdapterTestextends AbstractRedisAdapterTest
1519
{
1620
publicstaticfunctionsetupBeforeClass()
@@ -22,6 +26,33 @@ public static function setupBeforeClass()
2226
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
2327
}
2428

25-
self::$redis =new \RedisCluster(null,explode('',$hosts));
29+
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace('',']&host[',$hosts).']',array('lazy' =>true,'redis_cluster' =>true));
30+
}
31+
32+
publicfunctioncreateCachePool($defaultLifetime =0)
33+
{
34+
$this->assertInstanceOf(RedisClusterProxy::class,self::$redis);
35+
$adapter =newRedisAdapter(self::$redis,str_replace('\\','.',__CLASS__),$defaultLifetime);
36+
37+
return$adapter;
38+
}
39+
40+
/**
41+
* @dataProvider provideFailedCreateConnection
42+
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
43+
* @expectedExceptionMessage Redis connection failed
44+
*/
45+
publicfunctiontestFailedCreateConnection($dsn)
46+
{
47+
RedisAdapter::createConnection($dsn);
48+
}
49+
50+
publicfunctionprovideFailedCreateConnection()
51+
{
52+
returnarray(
53+
array('redis://localhost:1234?redis_cluster=1'),
54+
array('redis://foo@localhost?redis_cluster=1'),
55+
array('redis://localhost/123?redis_cluster=1'),
56+
);
2657
}
2758
}

‎src/Symfony/Component/Cache/Traits/RedisTrait.php‎

Lines changed: 140 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
usePredis\Connection\Aggregate\ClusterInterface;
1515
usePredis\Connection\Aggregate\RedisCluster;
16-
usePredis\Connection\Factory;
1716
usePredis\Response\Status;
1817
useSymfony\Component\Cache\Exception\CacheException;
1918
useSymfony\Component\Cache\Exception\InvalidArgumentException;
@@ -37,7 +36,10 @@ trait RedisTrait
3736
'retry_interval' =>0,
3837
'compression' =>true,
3938
'tcp_keepalive' =>0,
40-
'lazy' =>false,
39+
'lazy' =>null,
40+
'redis_cluster' =>false,
41+
'dbindex' =>0,
42+
'failover' =>'none',
4143
);
4244
private$redis;
4345
private$marshaller;
@@ -74,57 +76,87 @@ private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInt
7476
*
7577
* @throws InvalidArgumentException when the DSN is invalid
7678
*
77-
* @return \Redis|\Predis\Client According to the "class" option
79+
* @return \Redis|\RedisCluster|\Predis\Client According to the "class" option
7880
*/
7981
publicstaticfunctioncreateConnection($dsn,array$options =array())
8082
{
81-
if (0 !==strpos($dsn,'redis://')) {
82-
thrownewInvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis://"',$dsn));
83+
if (0 !==strpos($dsn,'redis:')) {
84+
thrownewInvalidArgumentException(sprintf('Invalid Redis DSN: %s does not start with "redis:"',$dsn));
8385
}
84-
$params =preg_replace_callback('#^redis://(?:(?:[^:@]*+:)?([^@]*+)@)?#',function ($m)use (&$auth) {
85-
if (isset($m[1])) {
86-
$auth =$m[1];
86+
87+
if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
88+
thrownewCacheException(sprintf('Cannot find the "redis" extension nor the "predis/predis" package: %s',$dsn));
89+
}
90+
91+
$params =preg_replace_callback('#^redis:(//)?(?:(?:[^:@]*+:)?([^@]*+)@)?#',function ($m)use (&$auth) {
92+
if (isset($m[2])) {
93+
$auth =$m[2];
8794
}
8895

89-
return'file://';
96+
return'file:'.($m[1] ??'');
9097
},$dsn);
91-
if (false ===$params =parse_url($params)) {
92-
thrownewInvalidArgumentException(sprintf('Invalid Redis DSN: %s',$dsn));
93-
}
94-
if (!isset($params['host']) && !isset($params['path'])) {
98+
99+
if (false ===$params =parse_url($dsn)) {
95100
thrownewInvalidArgumentException(sprintf('Invalid Redis DSN: %s',$dsn));
96101
}
97-
if (isset($params['path']) &&preg_match('#/(\d+)$#',$params['path'],$m)) {
98-
$params['dbindex'] =$m[1];
99-
$params['path'] =substr($params['path'],0, -\strlen($m[0]));
100-
}
101-
if (isset($params['host'])) {
102-
$scheme ='tcp';
103-
}else {
104-
$scheme ='unix';
105-
}
106-
$params +=array(
107-
'host' =>isset($params['host']) ?$params['host'] :$params['path'],
108-
'port' =>isset($params['host']) ?6379 :null,
109-
'dbindex' =>0,
110-
);
102+
103+
$query =$hosts =array();
104+
111105
if (isset($params['query'])) {
112106
parse_str($params['query'],$query);
113-
$params +=$query;
107+
108+
if (isset($query['host'])) {
109+
if (!\is_array($hosts =$query['host'])) {
110+
thrownewInvalidArgumentException(sprintf('Invalid Redis DSN: %s',$dsn));
111+
}
112+
foreach ($hostsas$host =>$parameters) {
113+
if (\is_string($parameters)) {
114+
parse_str($parameters,$parameters);
115+
}
116+
if (false ===$i =strrpos($host,':')) {
117+
$hosts[$host] =array('scheme' =>'tcp','host' =>$host,'port' =>6379) +$parameters;
118+
}elseif ($port = (int)substr($host,1 +$i)) {
119+
$hosts[$host] =array('scheme' =>'tcp','host' =>substr($host,0,$i),'port' =>$port) +$parameters;
120+
}else {
121+
$hosts[$host] =array('scheme' =>'unix','path' =>substr($host,0,$i)) +$parameters;
122+
}
123+
}
124+
$hosts =array_values($hosts);
125+
}
126+
}
127+
128+
if (isset($params['host']) ||isset($params['path'])) {
129+
if (!isset($params['dbindex']) &&isset($params['path']) &&preg_match('#/(\d+)$#',$params['path'],$m)) {
130+
$params['dbindex'] =$m[1];
131+
$params['path'] =substr($params['path'],0, -\strlen($m[0]));
132+
}
133+
134+
if (isset($params['host'])) {
135+
array_unshift($hosts,array('scheme' =>'tcp','host' =>$params['host'],'port' =>$params['port'] ??6379));
136+
}else {
137+
array_unshift($hosts,array('scheme' =>'unix','path' =>$params['path']));
138+
}
114139
}
115-
$params +=$options +self::$defaultConnectionOptions;
116-
if (null ===$params['class'] && !\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
117-
thrownewCacheException(sprintf('Cannot find the "redis" extension, and "predis/predis" is not installed: %s',$dsn));
140+
141+
if (!$hosts) {
142+
thrownewInvalidArgumentException(sprintf('Invalid Redis DSN: %s',$dsn));
143+
}
144+
145+
$params +=$query +$options +self::$defaultConnectionOptions;
146+
147+
if (null ===$params['class'] &&\extension_loaded('redis')) {
148+
$class =$params['redis_cluster'] ? \RedisCluster::class : (1 <\count($hosts) ? \RedisArray::class : \Redis::class);
149+
}else {
150+
$class =null ===$params['class'] ? \Predis\Client::class :$params['class'];
118151
}
119-
$class =null ===$params['class'] ? (\extension_loaded('redis') ? \Redis::class : \Predis\Client::class) :$params['class'];
120152

121153
if (is_a($class, \Redis::class,true)) {
122154
$connect =$params['persistent'] ||$params['persistent_id'] ?'pconnect' :'connect';
123155
$redis =new$class();
124156

125-
$initializer =function ($redis)use ($connect,$params,$dsn,$auth) {
157+
$initializer =function ($redis)use ($connect,$params,$dsn,$auth,$hosts) {
126158
try {
127-
@$redis->{$connect}($params['host'],$params['port'],$params['timeout'],$params['persistent_id'],$params['retry_interval']);
159+
@$redis->{$connect}($hosts[0]['host'],$hosts[0]['port'],$params['timeout'], (string)$params['persistent_id'],$params['retry_interval']);
128160
}catch (\RedisException$e) {
129161
thrownewInvalidArgumentException(sprintf('Redis connection failed (%s): %s',$e->getMessage(),$dsn));
130162
}
@@ -160,15 +192,82 @@ public static function createConnection($dsn, array $options = array())
160192
}else {
161193
$initializer($redis);
162194
}
195+
}elseif (is_a($class, \RedisArray::class,true)) {
196+
foreach ($hostsas$i =>$host) {
197+
$hosts[$i] ='tcp' ===$host['scheme'] ?$host['host'].':'.$host['port'] :$host['path'];
198+
}
199+
$params['lazy_connect'] =$params['lazy'] ??true;
200+
$params['connect_timeout'] =$params['timeout'];
201+
202+
try {
203+
$redis =new$class($hosts,$params);
204+
}catch (\RedisClusterException$e) {
205+
thrownewInvalidArgumentException(sprintf('Redis connection failed (%s): %s',$e->getMessage(),$dsn));
206+
}
207+
208+
if (0 <$params['tcp_keepalive'] &&\defined('Redis::OPT_TCP_KEEPALIVE')) {
209+
$redis->setOption(\Redis::OPT_TCP_KEEPALIVE,$params['tcp_keepalive']);
210+
}
211+
if ($params['compression'] &&\defined('Redis::COMPRESSION_LZF')) {
212+
$redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF);
213+
}
214+
}elseif (is_a($class, \RedisCluster::class,true)) {
215+
$initializer =function ()use ($class,$params,$dsn,$hosts) {
216+
foreach ($hostsas$i =>$host) {
217+
$hosts[$i] ='tcp' ===$host['scheme'] ?$host['host'].':'.$host['port'] :$host['path'];
218+
}
219+
220+
try {
221+
$redis =new$class(null,$hosts,$params['timeout'],$params['read_timeout'], (bool)$params['persistent']);
222+
}catch (\RedisClusterException$e) {
223+
thrownewInvalidArgumentException(sprintf('Redis connection failed (%s): %s',$e->getMessage(),$dsn));
224+
}
225+
226+
if (0 <$params['tcp_keepalive'] &&\defined('Redis::OPT_TCP_KEEPALIVE')) {
227+
$redis->setOption(\Redis::OPT_TCP_KEEPALIVE,$params['tcp_keepalive']);
228+
}
229+
if ($params['compression'] &&\defined('Redis::COMPRESSION_LZF')) {
230+
$redis->setOption(\Redis::OPT_COMPRESSION, \Redis::COMPRESSION_LZF);
231+
}
232+
switch ($params['failover']) {
233+
case'error':$redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_ERROR);break;
234+
case'distribute':$redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE);break;
235+
case'slaves':$redis->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES);break;
236+
}
237+
238+
return$redis;
239+
};
240+
241+
$redis =$params['lazy'] ?newRedisClusterProxy($initializer) :$initializer();
163242
}elseif (is_a($class, \Predis\Client::class,true)) {
164-
$params['scheme'] =$scheme;
165-
$params['database'] =$params['dbindex'] ?:null;
166-
$params['password'] =$auth;
167-
$redis =new$class((newFactory())->create($params));
243+
if ($params['redis_cluster']) {
244+
$params['cluster'] ='redis';
245+
}
246+
$params +=array('parameters' =>array());
247+
$params['parameters'] +=array(
248+
'persistent' =>$params['persistent'],
249+
'timeout' =>$params['timeout'],
250+
'read_write_timeout' =>$params['read_timeout'],
251+
'tcp_nodelay' =>true,
252+
);
253+
if ($params['dbindex']) {
254+
$params['parameters']['database'] =$params['dbindex'];
255+
}
256+
if (null !==$auth) {
257+
$params['parameters']['password'] =$auth;
258+
}
259+
if (1 ===\count($hosts) && !$params['redis_cluster']) {
260+
$hosts =$hosts[0];
261+
}elseif (\in_array($params['failover'],array('slaves','distribute'),true) && !isset($params['replication'])) {
262+
$params['replication'] =true;
263+
$hosts[0] +=array('alias' =>'master');
264+
}
265+
266+
$redis =new$class($hosts,array_diff_key($params,self::$defaultConnectionOptions));
168267
}elseif (class_exists($class,false)) {
169-
thrownewInvalidArgumentException(sprintf('"%s" is not a subclass of "Redis" or "Predis\Client"',$class));
268+
thrownewInvalidArgumentException(sprintf('"%s" is not a subclass of "Redis", "RedisArray", "RedisCluster" nor "Predis\Client".',$class));
170269
}else {
171-
thrownewInvalidArgumentException(sprintf('Class "%s" does not exist',$class));
270+
thrownewInvalidArgumentException(sprintf('Class "%s" does not exist.',$class));
172271
}
173272

174273
return$redis;
@@ -183,7 +282,6 @@ protected function doFetch(array $ids)
183282
returnarray();
184283
}
185284

186-
$i = -1;
187285
$result =array();
188286

189287
if ($this->redisinstanceof \Predis\Client) {
@@ -244,6 +342,7 @@ protected function doClear($namespace)
244342
$h->connect($host[0],$host[1]);
245343
}
246344
}
345+
247346
foreach ($hostsas$host) {
248347
if (!isset($namespace[0])) {
249348
$cleared =$host->flushDb() &&$cleared;

‎src/Symfony/Component/Cache/composer.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"cache/integration-tests":"dev-master",
3333
"doctrine/cache":"~1.6",
3434
"doctrine/dbal":"~2.5",
35-
"predis/predis":"~1.0",
35+
"predis/predis":"~1.1",
3636
"symfony/var-dumper":"^4.1.1"
3737
},
3838
"conflict": {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp