Merge pull request #831 from limingxinleo/1.1-redis

Fixed redis not reconnect after redis server restarted.
This commit is contained in:
李铭昕 2019-11-07 06:50:11 +08:00 committed by GitHub
commit bc166838f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 121 additions and 14 deletions

View File

@ -5,19 +5,20 @@
- [#812](https://github.com/hyperf/hyperf/pull/812) Added singleton crontab task support. - [#812](https://github.com/hyperf/hyperf/pull/812) Added singleton crontab task support.
- [#832](https://github.com/hyperf/hyperf/pull/832) Added `Hyperf\Utils\Codec\Json`. - [#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`. - [#833](https://github.com/hyperf/hyperf/pull/833) Added `Hyperf\Utils\Backoff`.
- [#852](https://github.com/hyperf/hyperf/pull/852) Added method `clear` for Parallel to clear callbacks. - [#852](https://github.com/hyperf/hyperf/pull/852) Added a `clear()` method for `Hyperf\Utils\Parallel` to clear adde callbacks.
## Fixed ## Fixed
- [#831](https://github.com/hyperf/hyperf/pull/831) Fixed Redis client can not reconnect the server after the Redis server restarted.
- [#835](https://github.com/hyperf/hyperf/pull/835) Fixed `Request::inputs` default value does not works. - [#835](https://github.com/hyperf/hyperf/pull/835) Fixed `Request::inputs` default value does not works.
- [#841](https://github.com/hyperf/hyperf/pull/841) Fixed migration does not take effect under multiple data sources. - [#841](https://github.com/hyperf/hyperf/pull/841) Fixed migration does not take effect under multiple data sources.
- [#844](https://github.com/hyperf/hyperf/pull/844) Fixed the composer.json reader support root namespace. - [#844](https://github.com/hyperf/hyperf/pull/844) Fixed the reader of `composer.json` does not support the root namespace.
- [#850](https://github.com/hyperf/hyperf/pull/850) Fixed logger group does not works when the name is same. - [#850](https://github.com/hyperf/hyperf/pull/850) Fixed logger group does not works when the name is same.
## Optimized ## Optimized
- [#832](https://github.com/hyperf/hyperf/pull/832) Optimized that response will throw a exception when json format failed. - [#832](https://github.com/hyperf/hyperf/pull/832) Optimized that response will throw a exception when json format failed.
- [#840](https://github.com/hyperf/hyperf/pull/840) Use swoole timer function from namespace. - [#840](https://github.com/hyperf/hyperf/pull/840) Use `\Swoole\Timer::*` to instead of `swoole_timer_*` functions.
# v1.1.4 - 2019-10-31 # v1.1.4 - 2019-10-31

View File

@ -64,6 +64,7 @@ class ConnectionResolver implements ConnectionResolverInterface
if (! $connection instanceof ConnectionInterface) { if (! $connection instanceof ConnectionInterface) {
$pool = $this->factory->getPool($name); $pool = $this->factory->getPool($name);
// When Mysql connect failed, it will be catched by `Hyperf\Database\Connectors\ConnectionFactory`.
$connection = $pool->get()->getConnection(); $connection = $pool->get()->getConnection();
Context::set($id, $connection); Context::set($id, $connection);
if (Coroutine::inCoroutine()) { if (Coroutine::inCoroutine()) {

View File

@ -14,7 +14,6 @@ namespace Hyperf\Redis\Pool;
use Hyperf\Di\Container; use Hyperf\Di\Container;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Swoole\Coroutine\Channel;
class PoolFactory class PoolFactory
{ {

View File

@ -39,6 +39,7 @@ class Redis
$connection = $this->getConnection($hasContextConnection); $connection = $this->getConnection($hasContextConnection);
try { try {
$connection = $connection->getConnection();
// Execute the command with the arguments. // Execute the command with the arguments.
$result = $connection->{$name}(...$arguments); $result = $connection->{$name}(...$arguments);
} finally { } finally {
@ -88,7 +89,7 @@ class Redis
} }
if (! $connection instanceof RedisConnection) { if (! $connection instanceof RedisConnection) {
$pool = $this->factory->getPool($this->poolName); $pool = $this->factory->getPool($this->poolName);
$connection = $pool->get()->getConnection(); return $pool->get();
} }
return $connection; return $connection;
} }

View File

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Hyperf\Redis; namespace Hyperf\Redis;
use Hyperf\Contract\ConnectionInterface; use Hyperf\Contract\ConnectionInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Pool\Connection as BaseConnection; use Hyperf\Pool\Connection as BaseConnection;
use Hyperf\Pool\Exception\ConnectionException; use Hyperf\Pool\Exception\ConnectionException;
use Hyperf\Pool\Pool; use Hyperf\Pool\Pool;
@ -53,7 +54,13 @@ class RedisConnection extends BaseConnection implements ConnectionInterface
public function __call($name, $arguments) public function __call($name, $arguments)
{ {
return $this->connection->{$name}(...$arguments); try {
$result = $this->connection->{$name}(...$arguments);
} catch (\Throwable $exception) {
$result = $this->retry($name, $arguments, $exception);
}
return $result;
} }
public function getActiveConnection() public function getActiveConnection()
@ -125,4 +132,20 @@ class RedisConnection extends BaseConnection implements ConnectionInterface
{ {
$this->database = $database; $this->database = $database;
} }
protected function retry($name, $arguments, \Throwable $exception)
{
$logger = $this->container->get(StdoutLoggerInterface::class);
$logger->warning(sprintf('Redis::__call failed, bacause ' . $exception->getMessage()));
try {
$this->reconnect();
$result = $this->connection->{$name}(...$arguments);
} catch (\Throwable $exception) {
$this->lastUseTime = 0.0;
throw $exception;
}
return $result;
}
} }

View File

@ -15,10 +15,15 @@ namespace HyperfTest\Redis;
use Hyperf\Config\Config; use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface; use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Container; use Hyperf\Di\Container;
use Hyperf\Pool\Channel;
use Hyperf\Pool\PoolOption;
use Hyperf\Redis\Frequency;
use Hyperf\Redis\Pool\PoolFactory; use Hyperf\Redis\Pool\PoolFactory;
use Hyperf\Redis\Pool\RedisPool; use Hyperf\Redis\Pool\RedisPool;
use Hyperf\Redis\Redis; use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext; use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Context;
use HyperfTest\Redis\Stub\RedisPoolFailedStub;
use HyperfTest\Redis\Stub\RedisPoolStub; use HyperfTest\Redis\Stub\RedisPoolStub;
use Mockery; use Mockery;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -32,6 +37,7 @@ class RedisTest extends TestCase
public function tearDown() public function tearDown()
{ {
Mockery::close(); Mockery::close();
Context::set('redis.connection.default', null);
} }
public function testRedisConnect() public function testRedisConnect()
@ -68,9 +74,38 @@ class RedisTest extends TestCase
$this->assertSame('db:0 name:get argument:xxxx', $res[0]); $this->assertSame('db:0 name:get argument:xxxx', $res[0]);
} }
public function testRedisReuseAfterThrowable()
{
$container = $this->getContainer();
$pool = new RedisPoolFailedStub($container, 'default');
$container->shouldReceive('make')->once()->with(RedisPool::class, ['name' => 'default'])->andReturn($pool);
$factory = new PoolFactory($container);
$redis = new Redis($factory);
try {
$redis->set('xxxx', 'yyyy');
} catch (\Throwable $exception) {
$this->assertSame('Get connection failed.', $exception->getMessage());
}
$this->assertSame(1, $pool->getConnectionsInChannel());
$this->assertSame(1, $pool->getCurrentConnections());
}
private function getRedis() private function getRedis()
{
$container = $this->getContainer();
$pool = new RedisPoolStub($container, 'default');
$container->shouldReceive('make')->once()->with(RedisPool::class, ['name' => 'default'])->andReturn($pool);
$factory = new PoolFactory($container);
return new Redis($factory);
}
private function getContainer()
{ {
$container = Mockery::mock(Container::class); $container = Mockery::mock(Container::class);
ApplicationContext::setContainer($container);
$container->shouldReceive('get')->once()->with(ConfigInterface::class)->andReturn(new Config([ $container->shouldReceive('get')->once()->with(ConfigInterface::class)->andReturn(new Config([
'redis' => [ 'redis' => [
'default' => [ 'default' => [
@ -89,13 +124,13 @@ class RedisTest extends TestCase
], ],
], ],
])); ]));
$pool = new RedisPoolStub($container, 'default'); $container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn(new Frequency());
$container->shouldReceive('make')->once()->with(RedisPool::class, ['name' => 'default'])->andReturn($pool); $container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($class, $args) {
return new PoolOption(...array_values($args));
ApplicationContext::setContainer($container); });
$container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($class, $args) {
$factory = new PoolFactory($container); return new Channel($args['size']);
});
return new Redis($factory); return $container;
} }
} }

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Redis\Stub;
class RedisConnectionFailedStub extends RedisConnectionStub
{
public function getConnection()
{
throw new \Exception('Get connection failed.');
}
}

View File

@ -39,6 +39,8 @@ class RedisConnectionStub extends RedisConnection
$this->db = $this->config['db']; $this->db = $this->config['db'];
$this->timeout = $this->config['timeout']; $this->timeout = $this->config['timeout'];
$this->lastUseTime = microtime(true);
return true; return true;
} }

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Redis\Stub;
use Hyperf\Contract\ConnectionInterface;
use Hyperf\Redis\Pool\RedisPool;
class RedisPoolFailedStub extends RedisPool
{
protected function createConnection(): ConnectionInterface
{
return new RedisConnectionFailedStub($this->container, $this, $this->config);
}
}