|
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,71 @@ 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 | + $cleared = true; |
| 169 | + $hosts = array($this->redis); |
| 170 | + $evalArgs = array(array($namespace), 0); |
| 171 | + |
| 172 | + if ($this->redis instanceof \Predis\Client) { |
| 173 | + $evalArgs = array(0, $namespace); |
| 174 | + |
| 175 | + $connection = $this->redis->getConnection(); |
| 176 | + if ($connection instanceof PredisCluster) { |
| 177 | + $hosts = array(); |
| 178 | + foreach ($connection as $c) { |
| 179 | + $hosts[] = new \Predis\Client($c); |
| 180 | + } |
| 181 | + } elseif ($connection instanceof RedisCluster) { |
| 182 | + return false; |
| 183 | + } |
| 184 | + } elseif ($this->redis instanceof \RedisArray) { |
| 185 | + $hosts = array(); |
| 186 | + foreach ($this->redis->_hosts() as $host) { |
| 187 | + $hosts[] = $this->redis->_instance($host); |
| 188 | + } |
| 189 | + } elseif ($this->redis instanceof \RedisCluster) { |
| 190 | + return false; |
| 191 | + } |
| 192 | + foreach ($hosts as $host) { |
| 193 | + if (!isset($namespace[0])) { |
| 194 | + $cleared = $host->flushDb() && $cleared; |
| 195 | + continue; |
| 196 | + } |
| 197 | + |
| 198 | + $info = $host->info('Server'); |
| 199 | + $info = isset($info['Server']) ? $info['Server'] : $info; |
| 200 | + |
| 201 | + if (!version_compare($info['redis_version'], '2.8', '>=')) { |
| 202 | + // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS |
| 203 | + // can hang your server when it is executed against large databases (millions of items). |
| 204 | + // Whenever you hit this scale, you should really consider upgrading to Redis 2.8 or above. |
| 205 | + $cleared = $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 return 1", $evalArgs[0], $evalArgs[1]) && $cleared; |
| 206 | + continue; |
| 207 | + } |
| 208 | + |
| 209 | + $cursor = null; |
| 210 | + do { |
| 211 | + $keys = $host instanceof \Predis\Client ? $host->scan($cursor, 'MATCH', $namespace.'*', 'COUNT', 1000) : $host->scan($cursor, $namespace.'*', 1000); |
| 212 | + if (isset($keys[1]) && is_array($keys[1])) { |
| 213 | + $cursor = $keys[0]; |
| 214 | + $keys = $keys[1]; |
| 215 | + } |
| 216 | + if ($keys) { |
| 217 | + $host->del($keys); |
| 218 | + } |
| 219 | + } while ($cursor = (int) $cursor); |
| 220 | + } |
| 221 | + |
| 222 | + return $cleared; |
| 223 | + } |
| 224 | + |
152 | 225 | /**
|
153 | 226 | * {@inheritdoc}
|
154 | 227 | */
|
@@ -195,4 +268,47 @@ protected function doSave(array $values, $lifetime)
|
195 | 268 |
|
196 | 269 | return $failed;
|
197 | 270 | }
|
| 271 | + |
| 272 | + private function execute($command, $id, array $args, $redis = null) |
| 273 | + { |
| 274 | + array_unshift($args, $id); |
| 275 | + call_user_func_array(array($redis ?: $this->redis, $command), $args); |
| 276 | + } |
| 277 | + |
| 278 | + private function pipeline(\Closure $callback) |
| 279 | + { |
| 280 | + $redis = $this->redis; |
| 281 | + |
| 282 | + try { |
| 283 | + if ($redis instanceof \Predis\Client) { |
| 284 | + $redis->pipeline(function ($pipe) use ($callback) { |
| 285 | + $this->redis = $pipe; |
| 286 | + $callback(array($this, 'execute')); |
| 287 | + }); |
| 288 | + } elseif ($redis instanceof \RedisArray) { |
| 289 | + $connections = array(); |
| 290 | + $callback(function ($command, $id, $args) use (&$connections) { |
| 291 | + if (!isset($connections[$h = $this->redis->_target($id)])) { |
| 292 | + $connections[$h] = $this->redis->_instance($h); |
| 293 | + $connections[$h]->multi(\Redis::PIPELINE); |
| 294 | + } |
| 295 | + $this->execute($command, $id, $args, $connections[$h]); |
| 296 | + }); |
| 297 | + foreach ($connections as $c) { |
| 298 | + $c->exec(); |
| 299 | + } |
| 300 | + } else { |
| 301 | + $pipe = $redis->multi(\Redis::PIPELINE); |
| 302 | + try { |
| 303 | + $callback(array($this, 'execute')); |
| 304 | + } finally { |
| 305 | + if ($pipe) { |
| 306 | + $redis->exec(); |
| 307 | + } |
| 308 | + } |
| 309 | + } |
| 310 | + } finally { |
| 311 | + $this->redis = $redis; |
| 312 | + } |
| 313 | + } |
198 | 314 | }
|
0 commit comments