diff --git a/.travis.yml b/.travis.yml index d750214c2..c6a7ccca1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,6 +38,6 @@ before_script: - composer config -g process-timeout 900 && composer update script: - - composer analyse src/di src/json-rpc src/tracer src/metric src/nats + - composer analyse src/di src/json-rpc src/tracer src/metric src/redis src/nats - composer test -- --exclude-group NonCoroutine - vendor/bin/phpunit --group NonCoroutine diff --git a/CHANGELOG.md b/CHANGELOG.md index e74903022..ba962b0ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,9 @@ - [#820](https://github.com/hyperf/hyperf/pull/820) Added nats component. - [#832](https://github.com/hyperf/hyperf/pull/832) Added `Hyperf\Utils\Codec\Json`. - [#833](https://github.com/hyperf/hyperf/pull/833) Added `Hyperf\Utils\Backoff`. -- [#852](https://github.com/hyperf/hyperf/pull/852) Added a `clear()` method for `Hyperf\Utils\Parallel` to clear adde callbacks. +- [#852](https://github.com/hyperf/hyperf/pull/852) Added a `clear()` method for `Hyperf\Utils\Parallel` to clear added callbacks. - [#854](https://github.com/hyperf/hyperf/pull/854) Added `GraphQLMiddleware`. +- [#873](https://github.com/hyperf/hyperf/pull/873) Added redis cluster. ## Fixed diff --git a/doc/zh/redis.md b/doc/zh/redis.md index 58a2aa7ee..b67dd294f 100644 --- a/doc/zh/redis.md +++ b/doc/zh/redis.md @@ -8,12 +8,15 @@ composer require hyperf/redis ## 配置 -| 配置项 | 类型 | 默认值 | 备注 | -|:------:|:-------:|:-----------:|:---------:| -| host | string | 'localhost' | Redis地址 | -| auth | string | 无 | 密码 | -| port | integer | 6379 | 端口 | -| db | integer | 0 | DB | +| 配置项 | 类型 | 默认值 | 备注 | +|:--------------:|:-------:|:-----------:|:------------------------------:| +| host | string | 'localhost' | Redis地址 | +| auth | string | 无 | 密码 | +| port | integer | 6379 | 端口 | +| db | integer | 0 | DB | +| cluster.enable | boolean | false | 是否集群模式 | +| cluster.name | string | null | 集群名 | +| cluster.seeds | array | [] | 集群连接地址数组 ['host:port'] | ```php env('REDIS_AUTH', ''), 'port' => (int) env('REDIS_PORT', 6379), 'db' => (int) env('REDIS_DB', 0), + 'cluster' => [ + 'enable' => (bool) env('REDIS_CLUSTER_ENABLE', false), + 'name' => null, + 'seeds' => [], + ], 'pool' => [ 'min_connections' => 1, 'max_connections' => 10, @@ -63,6 +71,11 @@ return [ 'auth' => env('REDIS_AUTH', ''), 'port' => (int) env('REDIS_PORT', 6379), 'db' => (int) env('REDIS_DB', 0), + 'cluster' => [ + 'enable' => (bool) env('REDIS_CLUSTER_ENABLE', false), + 'name' => null, + 'seeds' => [], + ], 'pool' => [ 'min_connections' => 1, 'max_connections' => 10, @@ -128,3 +141,59 @@ $redis = $container->get(RedisFactory::class)->get('foo'); $result = $redis->keys('*'); ``` +## 集群模式 + +### 使用 `name` + +配置 `cluster`,修改修改 `redis.ini`,也可以修改 `Dockerfile` 如下 + +``` + # - config PHP + && { \ + echo "upload_max_filesize=100M"; \ + echo "post_max_size=108M"; \ + echo "memory_limit=1024M"; \ + echo "date.timezone=${TIMEZONE}"; \ + echo "redis.clusters.seeds = \"mycluster[]=localhost:7000&mycluster[]=localhost:7001\""; \ + echo "redis.clusters.timeout = \"mycluster=5\""; \ + echo "redis.clusters.read_timeout = \"mycluster=10\""; \ + echo "redis.clusters.auth = \"mycluster=password\""; + } | tee conf.d/99-overrides.ini \ +``` + +对应 PHP 配置如下 + +```php + [ + 'cluster' => [ + 'enable' => true, + 'name' => 'mycluster', + 'seeds' => [], + ], + ], +]; +``` + +### 使用 seeds + +当然不配置 name 直接使用 seeds 也是可以的。如下 + +```php + [ + 'cluster' => [ + 'enable' => true, + 'name' => null, + 'seeds' => [ + '192.168.1.110:6379', + '192.168.1.111:6379', + ], + ], + ], +]; +``` \ No newline at end of file diff --git a/src/redis/publish/redis.php b/src/redis/publish/redis.php index 2386672d1..13b99c87d 100644 --- a/src/redis/publish/redis.php +++ b/src/redis/publish/redis.php @@ -19,6 +19,11 @@ return [ 'timeout' => 0.0, 'reserved' => null, 'retry_interval' => 0, + 'cluster' => [ + 'enable' => (bool) env('REDIS_CLUSTER_ENABLE', false), + 'name' => null, + 'seeds' => [], + ], 'pool' => [ 'min_connections' => 1, 'max_connections' => 10, diff --git a/src/redis/src/Exception/InvalidRedisConnectionException.php b/src/redis/src/Exception/InvalidRedisConnectionException.php new file mode 100644 index 000000000..0754cfae0 --- /dev/null +++ b/src/redis/src/Exception/InvalidRedisConnectionException.php @@ -0,0 +1,19 @@ +name; diff --git a/src/redis/src/Redis.php b/src/redis/src/Redis.php index 6afc22d84..e11c158c6 100644 --- a/src/redis/src/Redis.php +++ b/src/redis/src/Redis.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Hyperf\Redis; +use Hyperf\Redis\Exception\InvalidRedisConnectionException; use Hyperf\Redis\Pool\PoolFactory; use Hyperf\Utils\Context; @@ -89,7 +90,10 @@ class Redis } if (! $connection instanceof RedisConnection) { $pool = $this->factory->getPool($this->poolName); - return $pool->get(); + $connection = $pool->get(); + } + if (! $connection instanceof RedisConnection) { + throw new InvalidRedisConnectionException('The connection is not a valid RedisConnection.'); } return $connection; } diff --git a/src/redis/src/RedisConnection.php b/src/redis/src/RedisConnection.php index a308269c3..401cb0d70 100644 --- a/src/redis/src/RedisConnection.php +++ b/src/redis/src/RedisConnection.php @@ -19,6 +19,9 @@ use Hyperf\Pool\Exception\ConnectionException; use Hyperf\Pool\Pool; use Psr\Container\ContainerInterface; +/** + * @method bool select(int $db) + */ class RedisConnection extends BaseConnection implements ConnectionInterface { /** @@ -35,6 +38,11 @@ class RedisConnection extends BaseConnection implements ConnectionInterface 'auth' => null, 'db' => 0, 'timeout' => 0.0, + 'cluster' => [ + 'enable' => false, + 'name' => null, + 'seeds' => [], + ], 'options' => [], ]; @@ -83,10 +91,16 @@ class RedisConnection extends BaseConnection implements ConnectionInterface $auth = $this->config['auth']; $db = $this->config['db']; $timeout = $this->config['timeout']; + $cluster = $this->config['cluster']['enable'] ?? false; - $redis = new \Redis(); - if (! $redis->connect($host, $port, $timeout)) { - throw new ConnectionException('Connection reconnect failed.'); + $redis = null; + if ($cluster !== true) { + $redis = new \Redis(); + if (! $redis->connect($host, $port, $timeout)) { + throw new ConnectionException('Connection reconnect failed.'); + } + } else { + $redis = $this->createRedisCluster(); } $options = $this->config['options'] ?? []; @@ -133,6 +147,21 @@ class RedisConnection extends BaseConnection implements ConnectionInterface $this->database = $database; } + protected function createRedisCluster() + { + try { + $seeds = $this->config['cluster']['seeds'] ?? []; + $name = $this->config['cluster']['name'] ?? null; + $timeout = $this->config['timeout'] ?? null; + + $redis = new \RedisCluster($name, $seeds, $timeout); + } catch (\Throwable $e) { + throw new ConnectionException('Connection reconnect failed. ' . $e->getMessage()); + } + + return $redis; + } + protected function retry($name, $arguments, \Throwable $exception) { $logger = $this->container->get(StdoutLoggerInterface::class); diff --git a/src/redis/src/RedisFactory.php b/src/redis/src/RedisFactory.php index 663e33439..6630b1f5a 100644 --- a/src/redis/src/RedisFactory.php +++ b/src/redis/src/RedisFactory.php @@ -32,7 +32,7 @@ class RedisFactory } /** - * @return \Redis + * @return \Redis|RedisProxy */ public function get(string $poolName) { diff --git a/src/redis/tests/RedisConnectionTest.php b/src/redis/tests/RedisConnectionTest.php index 31b5d1189..de6e6f2a1 100644 --- a/src/redis/tests/RedisConnectionTest.php +++ b/src/redis/tests/RedisConnectionTest.php @@ -47,6 +47,11 @@ class RedisConnectionTest extends TestCase 'auth' => 'redis', 'db' => 0, 'timeout' => 0.0, + 'cluster' => [ + 'enable' => false, + 'name' => null, + 'seeds' => [], + ], 'options' => [], 'pool' => [ 'min_connections' => 1, diff --git a/src/redis/tests/Stub/RedisConnectionStub.php b/src/redis/tests/Stub/RedisConnectionStub.php index 546a1a01d..7f535a625 100644 --- a/src/redis/tests/Stub/RedisConnectionStub.php +++ b/src/redis/tests/Stub/RedisConnectionStub.php @@ -49,17 +49,11 @@ class RedisConnectionStub extends RedisConnection $this->db = $db; } - /** - * @return array - */ public function getConfig(): array { return $this->config; } - /** - * @return null|int - */ public function getDatabase(): ?int { return $this->database;