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.
- [#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 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
- [#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.
- [#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.
## Optimized
- [#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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Hyperf\Redis;
use Hyperf\Contract\ConnectionInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Pool\Connection as BaseConnection;
use Hyperf\Pool\Exception\ConnectionException;
use Hyperf\Pool\Pool;
@ -53,7 +54,13 @@ class RedisConnection extends BaseConnection implements ConnectionInterface
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()
@ -125,4 +132,20 @@ class RedisConnection extends BaseConnection implements ConnectionInterface
{
$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\Contract\ConfigInterface;
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\RedisPool;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Context;
use HyperfTest\Redis\Stub\RedisPoolFailedStub;
use HyperfTest\Redis\Stub\RedisPoolStub;
use Mockery;
use PHPUnit\Framework\TestCase;
@ -32,6 +37,7 @@ class RedisTest extends TestCase
public function tearDown()
{
Mockery::close();
Context::set('redis.connection.default', null);
}
public function testRedisConnect()
@ -68,9 +74,38 @@ class RedisTest extends TestCase
$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()
{
$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);
ApplicationContext::setContainer($container);
$container->shouldReceive('get')->once()->with(ConfigInterface::class)->andReturn(new Config([
'redis' => [
'default' => [
@ -89,13 +124,13 @@ class RedisTest extends TestCase
],
],
]));
$pool = new RedisPoolStub($container, 'default');
$container->shouldReceive('make')->once()->with(RedisPool::class, ['name' => 'default'])->andReturn($pool);
ApplicationContext::setContainer($container);
$factory = new PoolFactory($container);
return new Redis($factory);
$container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn(new Frequency());
$container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($class, $args) {
return new PoolOption(...array_values($args));
});
$container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($class, $args) {
return new Channel($args['size']);
});
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->timeout = $this->config['timeout'];
$this->lastUseTime = microtime(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);
}
}