|
12 | 12 | namespace Symfony\Component\Cache\Adapter; |
13 | 13 |
|
14 | 14 | use Predis\Connection\Factory; |
| 15 | +use Predis\Connection\Aggregate\PredisCluster; |
| 16 | +use Predis\Connection\Aggregate\RedisCluster; |
15 | 17 | use Symfony\Component\Cache\Exception\InvalidArgumentException; |
16 | 18 |
|
17 | 19 | /** |
|
20 | 22 | */ |
21 | 23 | class RedisAdapter extends AbstractAdapter |
22 | 24 | { |
23 | | - use RedisAdapterTrait; |
24 | | - |
25 | 25 | private static $defaultConnectionOptions = array( |
26 | 26 | 'class' => null, |
27 | 27 | 'persistent' => 0, |
28 | 28 | 'timeout' => 0, |
29 | 29 | 'read_timeout' => 0, |
30 | 30 | 'retry_interval' => 0, |
31 | 31 | ); |
| 32 | + private $redis; |
32 | 33 |
|
33 | 34 | /** |
34 | 35 | * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient |
35 | 36 | */ |
36 | 37 | public function __construct($redisClient, $namespace = '', $defaultLifetime = 0) |
37 | 38 | { |
38 | 39 | parent::__construct($namespace, $defaultLifetime); |
39 | | - $this->setRedis($redisClient, $namespace); |
| 40 | + |
| 41 | + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { |
| 42 | + throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); |
| 43 | + } |
| 44 | + if (!$redisClient instanceof \Redis && !$redisClient instanceof \RedisArray && !$redisClient instanceof \RedisCluster && !$redisClient instanceof \Predis\Client) { |
| 45 | + 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))); |
| 46 | + } |
| 47 | + $this->redis = $redisClient; |
40 | 48 | } |
41 | 49 |
|
42 | 50 | /** |
@@ -149,6 +157,51 @@ protected function doHave($id) |
149 | 157 | return (bool) $this->redis->exists($id); |
150 | 158 | } |
151 | 159 |
|
| 160 | + /** |
| 161 | + * {@inheritdoc} |
| 162 | + */ |
| 163 | + protected function doClear($namespace) |
| 164 | + { |
| 165 | + // When using a native Redis cluster, clearing the cache cannot work and always returns false. |
| 166 | + // Clearing the cache should then be done by any other means (e.g. by restarting the cluster). |
| 167 | + |
| 168 | + $hosts = array($this->redis); |
| 169 | + $evalArgs = array(array($namespace), 0); |
| 170 | + |
| 171 | + if ($this->redis instanceof \Predis\Client) { |
| 172 | + $evalArgs = array(0, $namespace); |
| 173 | + |
| 174 | + $connection = $this->redis->getConnection(); |
| 175 | + if ($connection instanceof PredisCluster) { |
| 176 | + $hosts = array(); |
| 177 | + foreach ($connection as $c) { |
| 178 | + $hosts[] = new \Predis\Client($c); |
| 179 | + } |
| 180 | + } elseif ($connection instanceof RedisCluster) { |
| 181 | + return false; |
| 182 | + } |
| 183 | + } elseif ($this->redis instanceof \RedisArray) { |
| 184 | + foreach ($this->redis->_hosts() as $host) { |
| 185 | + $hosts[] = $this->redis->_instance($host); |
| 186 | + } |
| 187 | + } elseif ($this->redis instanceof \RedisCluster) { |
| 188 | + return false; |
| 189 | + } |
| 190 | + foreach ($hosts as $host) { |
| 191 | + if (!isset($namespace[0])) { |
| 192 | + $host->flushDb(); |
| 193 | + } else { |
| 194 | + // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS |
| 195 | + // can hang your server when it is executed against large databases (millions of items). |
| 196 | + // Whenever you hit this scale, it is advised to deploy one Redis database per cache pool |
| 197 | + // instead of using namespaces, so that FLUSHDB is used instead. |
| 198 | + $host->eval("local keys=redis.call('KEYS',ARGV[1]..'*') for i=1,#keys,5000 do redis.call('DEL',unpack(keys,i,math.min(i+4999,#keys))) end", $evalArgs[0], $evalArgs[1]); |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + return true; |
| 203 | + } |
| 204 | + |
152 | 205 | /** |
153 | 206 | * {@inheritdoc} |
154 | 207 | */ |
@@ -195,4 +248,47 @@ protected function doSave(array $values, $lifetime) |
195 | 248 |
|
196 | 249 | return $failed; |
197 | 250 | } |
| 251 | + |
| 252 | + private function execute($command, $id, array $args, $redis = null) |
| 253 | + { |
| 254 | + array_unshift($args, $id); |
| 255 | + call_user_func_array(array($redis ?: $this->redis, $command), $args); |
| 256 | + } |
| 257 | + |
| 258 | + private function pipeline(\Closure $callback) |
| 259 | + { |
| 260 | + $redis = $this->redis; |
| 261 | + |
| 262 | + try { |
| 263 | + if ($redis instanceof \Predis\Client) { |
| 264 | + $redis->pipeline(function ($pipe) use ($callback) { |
| 265 | + $this->redis = $pipe; |
| 266 | + $callback(array($this, 'execute')); |
| 267 | + }); |
| 268 | + } elseif ($redis instanceof \RedisArray) { |
| 269 | + $connections = array(); |
| 270 | + $callback(function ($command, $id, $args) use (&$connections) { |
| 271 | + if (!isset($connections[$h = $this->redis->_target($id)])) { |
| 272 | + $connections[$h] = $this->redis->_instance($h); |
| 273 | + $connections[$h]->multi(\Redis::PIPELINE); |
| 274 | + } |
| 275 | + $this->execute($command, $id, $args, $connections[$h]); |
| 276 | + }); |
| 277 | + foreach ($connections as $c) { |
| 278 | + $c->exec(); |
| 279 | + } |
| 280 | + } else { |
| 281 | + $pipe = $redis->multi(\Redis::PIPELINE); |
| 282 | + try { |
| 283 | + $callback(array($this, 'execute')); |
| 284 | + } finally { |
| 285 | + if ($pipe) { |
| 286 | + $redis->exec(); |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | + } finally { |
| 291 | + $this->redis = $redis; |
| 292 | + } |
| 293 | + } |
198 | 294 | } |
0 commit comments