Skip to content

Commit

Permalink
feature #28598 [Cache] support configuring multiple Memcached servers…
Browse files Browse the repository at this point in the history
… in one DSN (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Cache] support configuring multiple Memcached servers in one DSN

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #27855
| License       | MIT
| Doc PR        | symfony/symfony-docs#10402

Useful to reconfigure dynamically an array of memcached servers (eg removing a dead one or adding a new one).
DSN format is e.g. `memcached://localhost?host[foo.bar]=3`.
To ease generating the DSN programmatically, it works also with `memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3`.

The key of the "host" parameter is a "host:port" pair, the value is the weight of the "host:port" pair.
Sockets need to be specified with the trailing `:` (as shown in the last example).

Commits
-------

8e0605a [Cache] support configuring multiple Memcached servers in one DSN
  • Loading branch information
fabpot committed Sep 26, 2018
2 parents 60fac5c + 8e0605a commit e6deb09
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 10 deletions.
Expand Up @@ -133,7 +133,7 @@ public static function getServiceProvider(ContainerBuilder $container, $name)
{
$container->resolveEnvPlaceholders($name, null, $usedEnvs);

if ($usedEnvs || preg_match('#^[a-z]++://#', $name)) {
if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
$dsn = $name;

if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/Adapter/AbstractAdapter.php
Expand Up @@ -151,7 +151,7 @@ public static function createConnection($dsn, array $options = array())
if (0 === strpos($dsn, 'redis://')) {
return RedisAdapter::createConnection($dsn, $options);
}
if (0 === strpos($dsn, 'memcached://')) {
if (0 === strpos($dsn, 'memcached:')) {
return MemcachedAdapter::createConnection($dsn, $options);
}

Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Cache/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
4.2.0
-----

* added support for configuring multiple Memcached servers in one DSN
* added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
* added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
* added sub-second expiry accuracy for backends that support it
Expand Down
42 changes: 42 additions & 0 deletions src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php
Expand Up @@ -192,4 +192,46 @@ public function provideDsnWithOptions()
array(\Memcached::OPT_SOCKET_RECV_SIZE => 1, \Memcached::OPT_SOCKET_SEND_SIZE => 2, \Memcached::OPT_RETRY_TIMEOUT => 8),
);
}

public function testMultiServerDsn()
{
$dsn = 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3';
$client = MemcachedAdapter::createConnection($dsn);

$expected = array(
0 => array(
'host' => 'localhost',
'port' => 11211,
'type' => 'TCP',
),
1 => array(
'host' => 'localhost',
'port' => 12345,
'type' => 'TCP',
),
2 => array(
'host' => '/some/memcached.sock',
'port' => 0,
'type' => 'SOCKET',
),
);
$this->assertSame($expected, $client->getServerList());

$dsn = 'memcached://localhost?host[foo.bar]=3';
$client = MemcachedAdapter::createConnection($dsn);

$expected = array(
0 => array(
'host' => 'localhost',
'port' => 11211,
'type' => 'TCP',
),
1 => array(
'host' => 'foo.bar',
'port' => 11211,
'type' => 'TCP',
),
);
$this->assertSame($expected, $client->getServerList());
}
}
43 changes: 35 additions & 8 deletions src/Symfony/Component/Cache/Traits/MemcachedTrait.php
Expand Up @@ -99,19 +99,43 @@ public static function createConnection($servers, array $options = array())
if (\is_array($dsn)) {
continue;
}
if (0 !== strpos($dsn, 'memcached://')) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached://"', $dsn));
if (0 !== strpos($dsn, 'memcached:')) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached:"', $dsn));
}
$params = preg_replace_callback('#^memcached://(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
if (!empty($m[1])) {
list($username, $password) = explode(':', $m[1], 2) + array(1 => null);
$params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
if (!empty($m[2])) {
list($username, $password) = explode(':', $m[2], 2) + array(1 => null);
}

return 'file://';
return 'file:'.($m[1] ?? '');
}, $dsn);
if (false === $params = parse_url($params)) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
}
$query = $hosts = array();
if (isset($params['query'])) {
parse_str($params['query'], $query);

if (isset($query['host'])) {
if (!\is_array($hosts = $query['host'])) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
}
foreach ($hosts as $host => $weight) {
if (false === $port = strrpos($host, ':')) {
$hosts[$host] = array($host, 11211, (int) $weight);
} else {
$hosts[$host] = array(substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight);
}
}
$hosts = array_values($hosts);
unset($query['host']);
}
if ($hosts && !isset($params['host']) && !isset($params['path'])) {
unset($servers[$i]);
$servers = array_merge($servers, $hosts);
continue;
}
}
if (!isset($params['host']) && !isset($params['path'])) {
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
}
Expand All @@ -124,13 +148,16 @@ public static function createConnection($servers, array $options = array())
'port' => isset($params['host']) ? 11211 : null,
'weight' => 0,
);
if (isset($params['query'])) {
parse_str($params['query'], $query);
if ($query) {
$params += $query;
$options = $query + $options;
}

$servers[$i] = array($params['host'], $params['port'], $params['weight']);

if ($hosts) {
$servers = array_merge($servers, $hosts);
}
}

// set client's options
Expand Down

0 comments on commit e6deb09

Please sign in to comment.