mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 18:27:44 +08:00
Added MemoryDriver
for hyperf/cache
(#6542)
Co-authored-by: 张城铭 <z@hyperf.io>
This commit is contained in:
parent
b4679a442e
commit
2e709cda58
@ -1,5 +1,9 @@
|
||||
# v3.1.10 - TBD
|
||||
|
||||
## Added
|
||||
|
||||
- [#6542](https://github.com/hyperf/hyperf/pull/6542) Added `MemoryDriver` for `hyperf/cache`.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#6539](https://github.com/hyperf/hyperf/pull/6539) Optimized `retry` helper function can accept array as first parameter.
|
||||
|
@ -298,6 +298,20 @@ class UserBookService
|
||||
|
||||
`Hyperf\Cache\Driver\RedisDriver` will store cache data in `Redis`, and users need to configure the corresponding `Redis configuration`. This mode is the default mode.
|
||||
|
||||
### Process memory driver
|
||||
|
||||
If you need to cache data into memory, you can try this driver. The configuration is as follows:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'memory' => [
|
||||
'driver' => Hyperf\Cache\Driver\MemoryDriver::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Coroutine memory driver
|
||||
|
||||
If you need to cache data into `Context`, you can try this driver. For example, in the following application scenario, `Demo::get` will be called multiple times in multiple places, but you do not want to query `Redis` every time.
|
||||
|
@ -299,6 +299,22 @@ class UserBookService
|
||||
|
||||
`Hyperf\Cache\Driver\RedisDriver` 会把缓存数据存放到 `Redis` 中,需要用户配置相应的 `Redis 配置`。此方式为默认方式。
|
||||
|
||||
### 进程内存驱动
|
||||
|
||||
如果您需要将数据缓存到内存中,可以尝试此驱动。
|
||||
|
||||
配置如下:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'memory' => [
|
||||
'driver' => Hyperf\Cache\Driver\MemoryDriver::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 协程内存驱动
|
||||
|
||||
如果您需要将数据缓存到 `Context` 中,可以尝试此驱动。例如以下应用场景 `Demo::get` 会在多个地方调用多次,但是又不想每次都到 `Redis` 中进行查询。
|
||||
|
@ -299,6 +299,22 @@ class UserBookService
|
||||
|
||||
`Hyperf\Cache\Driver\RedisDriver` 會把緩存數據存放到 `Redis` 中,需要用户配置相應的 `Redis 配置`。此方式為默認方式。
|
||||
|
||||
### 進程內存驅動
|
||||
|
||||
如果您需要將數據緩存到內存中,可以嘗試此驅動。
|
||||
|
||||
配置如下:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'memory' => [
|
||||
'driver' => Hyperf\Cache\Driver\MemoryDriver::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 協程內存驅動
|
||||
|
||||
如果您需要將數據緩存到 `Context` 中,可以嘗試此驅動。例如以下應用場景 `Demo::get` 會在多個地方調用多次,但是又不想每次都到 `Redis` 中進行查詢。
|
||||
|
@ -299,6 +299,22 @@ class UserBookService
|
||||
|
||||
`Hyperf\Cache\Driver\RedisDriver` 會把快取資料存放到 `Redis` 中,需要使用者配置相應的 `Redis 配置`。此方式為預設方式。
|
||||
|
||||
### 程序記憶體驅動
|
||||
|
||||
如果您需要將資料快取到記憶體中,可以嘗試此驅動。
|
||||
|
||||
配置如下:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'memory' => [
|
||||
'driver' => Hyperf\Cache\Driver\MemoryDriver::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 協程記憶體驅動
|
||||
|
||||
如果您需要將資料快取到 `Context` 中,可以嘗試此驅動。例如以下應用場景 `Demo::get` 會在多個地方呼叫多次,但是又不想每次都到 `Redis` 中進行查詢。
|
||||
|
120
src/cache/src/Collector/Memory.php
vendored
Normal file
120
src/cache/src/Collector/Memory.php
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Cache\Collector;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Coordinator\Constants;
|
||||
use Hyperf\Coordinator\CoordinatorManager;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hyperf\Stringable\Str;
|
||||
|
||||
final class Memory
|
||||
{
|
||||
/**
|
||||
* @var array<string, null|Carbon>
|
||||
*/
|
||||
private array $keys = [];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $values = [];
|
||||
|
||||
private ?int $loopCid = null;
|
||||
|
||||
private ?int $waitCloseCid = null;
|
||||
|
||||
private bool $stopped = false;
|
||||
|
||||
public static function instance(): static
|
||||
{
|
||||
static $instance;
|
||||
|
||||
return $instance ??= new self();
|
||||
}
|
||||
|
||||
public function size(): int
|
||||
{
|
||||
return count($this->keys);
|
||||
}
|
||||
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->keys[$key]);
|
||||
}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->values[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, ?Carbon $ttl = null): bool
|
||||
{
|
||||
$this->loop();
|
||||
$this->keys[$key] = $ttl;
|
||||
$this->values[$key] = $value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
unset($this->keys[$key], $this->values[$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
$this->keys = [];
|
||||
$this->values = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clearPrefix(string $prefix): bool
|
||||
{
|
||||
foreach ($this->keys as $key => $ttl) {
|
||||
if (Str::startsWith($key, $prefix)) {
|
||||
$this->delete($key);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function loop(): void
|
||||
{
|
||||
$this->loopCid ??= Coroutine::create(function () {
|
||||
while (true) {
|
||||
foreach ($this->keys as $key => $ttl) {
|
||||
if ($ttl instanceof Carbon && Carbon::now()->gt($ttl)) {
|
||||
$this->delete($key);
|
||||
}
|
||||
}
|
||||
sleep(1);
|
||||
if ($this->stopped || $this->size() === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->loopCid = null;
|
||||
$this->stopped = false;
|
||||
});
|
||||
|
||||
$this->waitCloseCid ??= Coroutine::create(function () {
|
||||
CoordinatorManager::until(Constants::WORKER_EXIT)->yield();
|
||||
$this->stopped = true;
|
||||
$this->clear();
|
||||
$this->waitCloseCid = null;
|
||||
});
|
||||
}
|
||||
}
|
136
src/cache/src/Driver/MemoryDriver.php
vendored
Normal file
136
src/cache/src/Driver/MemoryDriver.php
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Cache\Driver;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Hyperf\Cache\Collector\Memory;
|
||||
use Hyperf\Cache\Exception\InvalidArgumentException;
|
||||
use Hyperf\Cache\Exception\OverflowException;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class MemoryDriver extends Driver implements DriverInterface
|
||||
{
|
||||
protected ?int $size = null;
|
||||
|
||||
protected bool $throwWhenSizeExceeded = false;
|
||||
|
||||
public function __construct(ContainerInterface $container, array $config)
|
||||
{
|
||||
parent::__construct($container, $config);
|
||||
|
||||
if (isset($config['size'])) {
|
||||
$this->size = (int) $config['size'];
|
||||
}
|
||||
if (isset($config['throw_when_size_exceeded'])) {
|
||||
$this->throwWhenSizeExceeded = (bool) $config['throw_when_size_exceeded'];
|
||||
}
|
||||
}
|
||||
|
||||
public function fetch($key, $default = null): array
|
||||
{
|
||||
return [
|
||||
$this->has($key),
|
||||
$this->get($key, $default),
|
||||
];
|
||||
}
|
||||
|
||||
public function has($key): bool
|
||||
{
|
||||
return $this->getCollector()->has($this->getCacheKey($key));
|
||||
}
|
||||
|
||||
public function get($key, $default = null): mixed
|
||||
{
|
||||
return $this->getCollector()->get(
|
||||
$this->getCacheKey($key),
|
||||
$default
|
||||
);
|
||||
}
|
||||
|
||||
public function set($key, $value, $ttl = null): bool
|
||||
{
|
||||
if (
|
||||
$this->size > 0
|
||||
&& $this->getCollector()->size() >= $this->size
|
||||
) {
|
||||
if ($this->throwWhenSizeExceeded) {
|
||||
throw new OverflowException('The memory cache is full!');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$seconds = $this->secondsUntil($ttl);
|
||||
return $this->getCollector()->set(
|
||||
$this->getCacheKey($key),
|
||||
$value,
|
||||
$seconds <= 0 ? null : Carbon::now()->addSeconds($seconds)
|
||||
);
|
||||
}
|
||||
|
||||
public function delete($key): bool
|
||||
{
|
||||
return $this->getCollector()->delete($this->getCacheKey($key));
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
return $this->getCollector()->clear();
|
||||
}
|
||||
|
||||
public function getMultiple($keys, $default = null): iterable
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$result[$key] = $this->get($key, $default);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function setMultiple($values, $ttl = null): bool
|
||||
{
|
||||
if (! is_array($values)) {
|
||||
throw new InvalidArgumentException('The values is invalid!');
|
||||
}
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$this->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteMultiple($keys): bool
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
$this->delete($key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clearPrefix(string $prefix): bool
|
||||
{
|
||||
return $this->getCollector()->clearPrefix($this->getCacheKey($prefix));
|
||||
}
|
||||
|
||||
public function getConnection(): mixed
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getCollector(): Memory
|
||||
{
|
||||
return Memory::instance();
|
||||
}
|
||||
}
|
16
src/cache/src/Exception/OverflowException.php
vendored
Normal file
16
src/cache/src/Exception/OverflowException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Cache\Exception;
|
||||
|
||||
class OverflowException extends CacheException
|
||||
{
|
||||
}
|
81
src/cache/tests/Cases/MemoryDriverTest.php
vendored
Normal file
81
src/cache/tests/Cases/MemoryDriverTest.php
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace HyperfTest\Cache\Cases;
|
||||
|
||||
use Hyperf\Cache\Collector\Memory;
|
||||
use Hyperf\Cache\Driver\MemoryDriver;
|
||||
use Hyperf\Cache\Exception\OverflowException;
|
||||
use Hyperf\Codec\Packer\PhpSerializerPacker;
|
||||
use Mockery;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class MemoryDriverTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
Memory::instance()->clear();
|
||||
}
|
||||
|
||||
public function testSetAndGet()
|
||||
{
|
||||
$driver = new MemoryDriver($this->getContainer(), []);
|
||||
|
||||
$driver->set('test', 'xxx');
|
||||
$this->assertSame('xxx', $driver->get('test'));
|
||||
}
|
||||
|
||||
public function testSetWithTtl()
|
||||
{
|
||||
$driver = new MemoryDriver($this->getContainer(), []);
|
||||
|
||||
$driver->set('test', 'xxx', 1);
|
||||
$this->assertSame('xxx', $driver->get('test'));
|
||||
|
||||
sleep(3);
|
||||
|
||||
$this->assertNull($driver->get('test'));
|
||||
}
|
||||
|
||||
public function testSetWithSize()
|
||||
{
|
||||
$driver = new MemoryDriver($this->getContainer(), ['size' => 1, 'throw_when_size_exceeded' => true]);
|
||||
|
||||
$driver->set('test1', 'xxx');
|
||||
$this->assertSame('xxx', $driver->get('test1'));
|
||||
|
||||
$this->expectException(OverflowException::class);
|
||||
$this->expectExceptionMessage('The memory cache is full!');
|
||||
$driver->set('test2', 'xxx');
|
||||
}
|
||||
|
||||
public function testSetWithSizeAndThrowWhenSizeExceededIsFalse()
|
||||
{
|
||||
$driver = new MemoryDriver($this->getContainer(), ['size' => 1, 'throw_when_size_exceeded' => false]);
|
||||
|
||||
$driver->set('test1', 'xxx');
|
||||
$this->assertSame('xxx', $driver->get('test1'));
|
||||
|
||||
$this->assertFalse($driver->set('test2', 'xxx'));
|
||||
}
|
||||
|
||||
private function getContainer(): ContainerInterface
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
$container->shouldReceive('get')->with(PhpSerializerPacker::class)->andReturn(new PhpSerializerPacker());
|
||||
return $container;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user