mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Merge pull request #444 from acelaya/feature/redis-support
Feature/redis support
This commit is contained in:
commit
b0bb77ca81
17 changed files with 163 additions and 13 deletions
|
@ -52,6 +52,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
|||
#### Changed
|
||||
|
||||
* [#430](https://github.com/shlinkio/shlink/issues/430) Updated to [shlinkio/php-coding-standard](https://github.com/shlinkio/php-coding-standard) 1.2.2
|
||||
* [#305](https://github.com/shlinkio/shlink/issues/305) Implemented changes which will allow Shlink to be truly clusterizable.
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"monolog/monolog": "^1.21",
|
||||
"ocramius/proxy-manager": "^2.0",
|
||||
"phly/phly-event-dispatcher": "^1.0",
|
||||
"predis/predis": "^1.1",
|
||||
"shlinkio/shlink-installer": "^1.2.1",
|
||||
"symfony/console": "^4.3",
|
||||
"symfony/filesystem": "^4.3",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
use Symfony\Component\Lock;
|
||||
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;
|
||||
|
||||
|
@ -13,13 +14,20 @@ return [
|
|||
'dependencies' => [
|
||||
'factories' => [
|
||||
Lock\Store\FlockStore::class => ConfigAbstractFactory::class,
|
||||
Lock\Store\RedisStore::class => ConfigAbstractFactory::class,
|
||||
Lock\Factory::class => ConfigAbstractFactory::class,
|
||||
],
|
||||
'aliases' => [
|
||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||
'lock_store' => Lock\Store\FlockStore::class,
|
||||
'redis_lock_store' => Lock\Store\RedisStore::class,
|
||||
],
|
||||
],
|
||||
|
||||
ConfigAbstractFactory::class => [
|
||||
Lock\Store\FlockStore::class => ['config.locks.locks_dir'],
|
||||
Lock\Factory::class => [Lock\Store\FlockStore::class],
|
||||
Lock\Store\RedisStore::class => [RedisFactory::SERVICE_NAME],
|
||||
Lock\Factory::class => ['lock_store'],
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -58,8 +58,8 @@ return [
|
|||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
'Logger_Shlink' => Common\Factory\LoggerFactory::class,
|
||||
'Logger_Access' => Common\Factory\LoggerFactory::class,
|
||||
'Logger_Shlink' => Common\Logger\LoggerFactory::class,
|
||||
'Logger_Access' => Common\Logger\LoggerFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
|
20
config/autoload/redis.local.php.local
Normal file
20
config/autoload/redis.local.php.local
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'redis' => [
|
||||
'servers' => 'tcp://shlink_redis:6379',
|
||||
// 'servers' => [
|
||||
// 'tcp://shlink_redis:6379',
|
||||
// ],
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'aliases' => [
|
||||
// With this config, a user could alias 'lock_store' => 'redis_lock_store' to override the default
|
||||
// 'lock_store' => 'redis_lock_store',
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -22,10 +22,9 @@ return (new ConfigAggregator\ConfigAggregator([
|
|||
Rest\ConfigProvider::class,
|
||||
EventDispatcher\ConfigProvider::class,
|
||||
new ConfigAggregator\PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
|
||||
new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
env('APP_ENV') === 'test'
|
||||
? new ConfigAggregator\PhpFileProvider('config/test/*.global.php')
|
||||
: new ConfigAggregator\ArrayProvider([]),
|
||||
: new ConfigAggregator\ZendConfigProvider('config/params/{generated_config.php,*.config.{php,json}}'),
|
||||
], 'data/cache/app_config.php', [
|
||||
Core\ConfigPostProcessor::class,
|
||||
]))->getMergedConfig();
|
||||
|
|
|
@ -24,6 +24,7 @@ services:
|
|||
links:
|
||||
- shlink_db
|
||||
- shlink_db_postgres
|
||||
- shlink_redis
|
||||
|
||||
shlink_swoole:
|
||||
container_name: shlink_swoole
|
||||
|
@ -37,6 +38,7 @@ services:
|
|||
links:
|
||||
- shlink_db
|
||||
- shlink_db_postgres
|
||||
- shlink_redis
|
||||
|
||||
shlink_db:
|
||||
container_name: shlink_db
|
||||
|
@ -62,3 +64,9 @@ services:
|
|||
POSTGRES_PASSWORD: root
|
||||
POSTGRES_DB: shlink
|
||||
PGDATA: /var/lib/postgresql/data/pgdata
|
||||
|
||||
shlink_redis:
|
||||
container_name: shlink_redis
|
||||
image: redis:5.0-alpine
|
||||
ports:
|
||||
- "6380:6379"
|
||||
|
|
17
module/Common/config/cache.config.php
Normal file
17
module/Common/config/cache.config.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use Doctrine\Common\Cache as DoctrineCache;
|
||||
|
||||
return [
|
||||
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
DoctrineCache\Cache::class => Cache\CacheFactory::class,
|
||||
Cache\RedisFactory::SERVICE_NAME => Cache\RedisFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -3,7 +3,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use GeoIp2\Database\Reader;
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use Monolog\Logger;
|
||||
|
@ -20,7 +19,6 @@ return [
|
|||
'dependencies' => [
|
||||
'factories' => [
|
||||
GuzzleClient::class => InvokableFactory::class,
|
||||
Cache::class => Factory\CacheFactory::class,
|
||||
Filesystem::class => InvokableFactory::class,
|
||||
Reader::class => ConfigAbstractFactory::class,
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
namespace Shlinkio\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
@ -14,6 +14,8 @@ class CacheFactory implements FactoryInterface
|
|||
{
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): Cache\Cache
|
||||
{
|
||||
// TODO Make use of the redis cache via RedisFactory when possible
|
||||
|
||||
$appOptions = $container->get(AppOptions::class);
|
||||
$adapter = env('APP_ENV', 'pro') === 'pro' ? new Cache\ApcuCache() : new Cache\ArrayCache();
|
||||
$adapter->setNamespace((string) $appOptions);
|
30
module/Common/src/Cache/RedisFactory.php
Normal file
30
module/Common/src/Cache/RedisFactory.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Cache;
|
||||
|
||||
use Predis\Client as PredisClient;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
class RedisFactory
|
||||
{
|
||||
public const SERVICE_NAME = 'Shlinkio\Shlink\Common\Cache\Redis';
|
||||
|
||||
public function __invoke(ContainerInterface $container): PredisClient
|
||||
{
|
||||
$redisConfig = $container->get('config')['redis'] ?? [];
|
||||
$servers = $redisConfig['servers'] ?? [];
|
||||
|
||||
if (is_array($servers) && count($servers) === 1) {
|
||||
$servers = array_shift($servers);
|
||||
}
|
||||
|
||||
$options = is_string($servers) || count($servers) < 1 ? null : ['cluster' => 'redis'];
|
||||
return new PredisClient($servers, $options);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Factory;
|
||||
namespace Shlinkio\Shlink\Common\Logger;
|
||||
|
||||
use Cascade\Cascade;
|
||||
use Interop\Container\ContainerInterface;
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
namespace ShlinkioTest\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache\ApcuCache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Common\Factory\CacheFactory;
|
||||
use Shlinkio\Shlink\Common\Cache\CacheFactory;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
58
module/Common/test/Cache/RedisFactoryTest.php
Normal file
58
module/Common/test/Cache/RedisFactoryTest.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Cache;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\Connection\Aggregate\PredisCluster;
|
||||
use Predis\Connection\Aggregate\RedisCluster;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
|
||||
class RedisFactoryTest extends TestCase
|
||||
{
|
||||
/** @var RedisFactory */
|
||||
private $factory;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
$this->factory = new RedisFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideRedisConfig
|
||||
*/
|
||||
public function createsRedisClientBasedOnConfig(?array $config, string $expectedCluster): void
|
||||
{
|
||||
$getConfig = $this->container->get('config')->willReturn([
|
||||
'redis' => $config,
|
||||
]);
|
||||
|
||||
$client = ($this->factory)($this->container->reveal());
|
||||
|
||||
$getConfig->shouldHaveBeenCalledOnce();
|
||||
$this->assertInstanceOf($expectedCluster, $client->getOptions()->cluster);
|
||||
}
|
||||
|
||||
public function provideRedisConfig(): iterable
|
||||
{
|
||||
yield 'no config' => [null, PredisCluster::class];
|
||||
yield 'single server as string' => [[
|
||||
'servers' => 'tcp://127.0.0.1:6379',
|
||||
], PredisCluster::class];
|
||||
yield 'single server as array' => [[
|
||||
'servers' => ['tcp://127.0.0.1:6379'],
|
||||
], PredisCluster::class];
|
||||
yield 'cluster of servers' => [[
|
||||
'servers' => ['tcp://1.1.1.1:6379', 'tcp://2.2.2.2:6379'],
|
||||
], RedisCluster::class];
|
||||
yield 'empty cluster of servers' => [[
|
||||
'servers' => [],
|
||||
], PredisCluster::class];
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkioTest\Shlink\Common\Factory;
|
||||
namespace ShlinkioTest\Shlink\Common\Logger;
|
||||
|
||||
use Monolog\Logger;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\Factory\LoggerFactory;
|
||||
use Shlinkio\Shlink\Common\Logger\LoggerFactory;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
class LoggerFactoryTest extends TestCase
|
|
@ -22,6 +22,7 @@ class ConfigPostProcessor
|
|||
'db_config' => ['entity_manager', 'connection'],
|
||||
'delete_short_url_threshold' => ['delete_short_urls', 'visits_threshold'],
|
||||
'locale' => ['translator', 'locale'],
|
||||
'lock_store' => ['dependencies', 'aliases', 'lock_store'],
|
||||
];
|
||||
private const SIMPLIFIED_CONFIG_TOGGLES = [
|
||||
'not_found_redirect_to' => ['url_shortener', 'not_found_short_url', 'enable_redirection'],
|
||||
|
|
|
@ -41,6 +41,7 @@ class ConfigPostProcessorTest extends TestCase
|
|||
'delete_short_url_threshold' => 50,
|
||||
'locale' => 'es',
|
||||
'not_found_redirect_to' => 'foobar.com',
|
||||
'lock_store' => 'redis_lock_store',
|
||||
'db_config' => [
|
||||
'dbname' => 'shlink',
|
||||
'user' => 'foo',
|
||||
|
@ -84,6 +85,12 @@ class ConfigPostProcessorTest extends TestCase
|
|||
'visits_threshold' => 50,
|
||||
'check_visits_threshold' => true,
|
||||
],
|
||||
|
||||
'dependencies' => [
|
||||
'aliases' => [
|
||||
'lock_store' => 'redis_lock_store',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = ($this->postProcessor)(array_merge($config, $simplified));
|
||||
|
|
Loading…
Add table
Reference in a new issue