From 319b3aecec60c6cc235cbc8116973da31771a6e1 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Thu, 4 Jan 2024 14:18:00 +0800 Subject: [PATCH 01/19] update CI support 8.0 --- .github/workflows/CI.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 77d9a8a..10c3161 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,6 +15,7 @@ jobs: - "8.3" - "8.2" - "8.1" + - "8.0" steps: - uses: actions/checkout@v3 - uses: shivammathur/setup-php@v2 From a7763f4295c80bd13148ef9eca6f34a0ad75bce0 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Thu, 4 Jan 2024 14:20:56 +0800 Subject: [PATCH 02/19] update CI support 8.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2dee76a..b159ced 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "source": "https://github.com/workbunny/webman-shared-cache" }, "require": { - "php": "^8.1", + "php": "^8.0", "ext-apcu": "*" }, "require-dev": { From fc12763dc15dd101784e2f259c30bf86df9ecd72 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Thu, 4 Jan 2024 14:31:49 +0800 Subject: [PATCH 03/19] update README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f4ae2c..92e77b2 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,8 @@
- - - Latest Stable Version Latest Stable Version From 38df8defdec9d4cd1080f2cc7156cb1629df5719 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Mon, 6 May 2024 15:32:32 +0800 Subject: [PATCH 04/19] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20ChannelMethods::?= =?UTF-8?q?SetChannelListenerInterval=20=E7=94=A8=E4=BA=8E=E8=AE=BE?= =?UTF-8?q?=E7=BD=AEchannel=20listener=E7=9A=84=E7=9B=91=E5=90=AC=E9=97=B4?= =?UTF-8?q?=E9=9A=94=20-=20=E8=B0=83=E6=95=B4=20Future::add=20=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=A2=9E=E5=8A=A0=20float|int|null=20$interval=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=97=B4=E9=9A=94=E6=94=B9=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Future.php | 5 +++-- src/Traits/ChannelMethods.php | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Future.php b/src/Future.php index f076526..f0ee053 100644 --- a/src/Future.php +++ b/src/Future.php @@ -22,9 +22,10 @@ class Future /** * @param Closure $func * @param array $args + * @param float|int|null $interval * @return int|false */ - public static function add(Closure $func, array $args = []): int|false + public static function add(Closure $func, array $args = [], float|int|null $interval = null): int|false { if (self::$debug) { self::$debugFunc = $func; @@ -37,7 +38,7 @@ public static function add(Closure $func, array $args = []): int|false } if ($id = Worker::$globalEvent->add( - Worker::$eventLoopClass === Event::class ? 0 : 0.001, + $interval ?: (Worker::$eventLoopClass === Event::class ? 0 : 0.001), EventInterface::EV_TIMER, $func, $args diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index 2e27c19..91d6cd8 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -25,6 +25,20 @@ trait ChannelMethods */ protected static array $_listeners = []; + /** + * @var float|int|null + */ + protected static float|int|null $interval = null; + + /** + * @param float|int|null $interval + * @return void + */ + public static function SetChannelListenerInterval(float|int|null $interval): void + { + self::$interval = $interval; + } + /** * @param string $key * @return string @@ -151,7 +165,7 @@ protected static function _ChCreateListener(string $key, string|int $workerId, C } }); - }); + }, interval: self::$interval); $channel[$workerId]['value'] = []; // 如果存在默认数据 if ($default = $channel['--default--']['value'] ?? []) { From e262eb8c861f660fb88795459eaee8de7b020acb Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 17 May 2024 15:45:06 +0800 Subject: [PATCH 05/19] fixed #4 #3 --- src/Traits/ChannelMethods.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index 91d6cd8..bb4fc64 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -21,10 +21,15 @@ trait ChannelMethods protected static string $_CHANNEL = '#Channel#'; /** - * @var array = [channelKey => listeners] + * @var array = [channelKey => futureId] */ protected static array $_listeners = []; + /** + * @var array = [channelKey => callback] + */ + protected static array $_listenerCallbacks = []; + /** * @var float|int|null */ @@ -152,7 +157,7 @@ protected static function _ChCreateListener(string $key, string|int $workerId, C // 设置回调 $channel[$workerId]['futureId'] = self::$_listeners[$key] = - $result = Future::add(function () use ($key, $workerId) { + $result = Future::add(self::$_listenerCallbacks[$key] = function () use ($key, $workerId) { // 原子性执行 self::_Atomic($key, function () use ($key, $workerId) { $channel = self::_Get($channelName = self::GetChannelKey($key), []); @@ -160,7 +165,7 @@ protected static function _ChCreateListener(string $key, string|int $workerId, C // 先进先出 $msg = array_shift($value); $channel[$workerId]['value'] = $value; - call_user_func(self::$_listeners[$key], $key, $workerId, $msg); + call_user_func(self::$_listenerCallbacks[$key], $key, $workerId, $msg); self::_Set($channelName, $channel); } @@ -215,8 +220,7 @@ protected static function _ChRemoveListener(string $key, string|int $workerId, b unset($channel[$workerId]); self::_Set($channelName, $channel); } - unset(self::$_listeners[$key]); - + unset(self::$_listeners[$key], self::$_listenerCallbacks[$key]); } return [ From bd8ad9dbd5c2ab8d3c8120cdd2fe6f4ba2c16e99 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 17 May 2024 15:48:12 +0800 Subject: [PATCH 06/19] fixed #3 --- src/Traits/ChannelMethods.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index bb4fc64..7001d06 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -142,7 +142,7 @@ protected static function _ChCreateListener(string $key, string|int $workerId, C throw new Error("Channel $key listener already exist. "); } self::_Atomic($key, function () use ( - $key, $workerId, $func, $params, &$result + $key, $workerId, $func, $params, $listener, &$result ) { /** * [ @@ -156,8 +156,9 @@ protected static function _ChCreateListener(string $key, string|int $workerId, C // 设置回调 $channel[$workerId]['futureId'] = + self::$_listenerCallbacks[$key] = $listener; self::$_listeners[$key] = - $result = Future::add(self::$_listenerCallbacks[$key] = function () use ($key, $workerId) { + $result = Future::add(function () use ($key, $workerId) { // 原子性执行 self::_Atomic($key, function () use ($key, $workerId) { $channel = self::_Get($channelName = self::GetChannelKey($key), []); From df45fccd4456ce6a93953d0523b99879335772a5 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 17 May 2024 15:49:52 +0800 Subject: [PATCH 07/19] save --- src/Traits/ChannelMethods.php | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index 7001d06..23af34d 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -25,11 +25,6 @@ trait ChannelMethods */ protected static array $_listeners = []; - /** - * @var array = [channelKey => callback] - */ - protected static array $_listenerCallbacks = []; - /** * @var float|int|null */ @@ -156,17 +151,16 @@ protected static function _ChCreateListener(string $key, string|int $workerId, C // 设置回调 $channel[$workerId]['futureId'] = - self::$_listenerCallbacks[$key] = $listener; self::$_listeners[$key] = - $result = Future::add(function () use ($key, $workerId) { + $result = Future::add(function () use ($key, $workerId, $listener) { // 原子性执行 - self::_Atomic($key, function () use ($key, $workerId) { + self::_Atomic($key, function () use ($key, $workerId, $listener) { $channel = self::_Get($channelName = self::GetChannelKey($key), []); if ((!empty($value = $channel[$workerId]['value'] ?? []))) { // 先进先出 $msg = array_shift($value); $channel[$workerId]['value'] = $value; - call_user_func(self::$_listenerCallbacks[$key], $key, $workerId, $msg); + call_user_func($listener, $key, $workerId, $msg); self::_Set($channelName, $channel); } @@ -221,7 +215,7 @@ protected static function _ChRemoveListener(string $key, string|int $workerId, b unset($channel[$workerId]); self::_Set($channelName, $channel); } - unset(self::$_listeners[$key], self::$_listenerCallbacks[$key]); + unset(self::$_listeners[$key]); } return [ From b1ca99f45fdea9d9813138f8df251cc9e1ba2dbb Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 17 May 2024 18:29:46 +0800 Subject: [PATCH 08/19] Fixed a bug where channels could not specify workerId publication --- src/Traits/ChannelMethods.php | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index 23af34d..d3e7d16 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -80,7 +80,7 @@ protected static function _ChPublish(string $key, mixed $message, bool $store = $func = __FUNCTION__; $params = func_get_args(); self::_Atomic($key, function () use ( - $key, $message, $func, $params, $store + $key, $message, $func, $params, $store, $workerId ) { /** * [ @@ -94,16 +94,33 @@ protected static function _ChPublish(string $key, mixed $message, bool $store = // 如果还没有监听器,将数据投入默认 if (!$channel) { if ($store) { - $channel['--default--']['value'][] = $message; + // 非指定workerId + if ($workerId !== null) { + $channel['--default--']['value'][] = $message; + } + // 指定workerId + else { + $channel[$workerId]['value'][] = $message; + } + } } // 否则将消息投入到每个worker的监听器数据中 else { - foreach ($channel as $workerId => $item) { - if ($store or isset($item['futureId'])) { + // 非指定workerId + if ($workerId !== null) { + if ($store or isset($channel[$workerId]['futureId'])) { $channel[$workerId]['value'][] = $message; } } + // 指定workerId + else { + foreach ($channel as $workerId => $item) { + if ($store or isset($item['futureId'])) { + $channel[$workerId]['value'][] = $message; + } + } + } } self::_Set($channelName, $channel); From f589eb7679288df89b97e89039479013b347c8c5 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 17 May 2024 18:34:07 +0800 Subject: [PATCH 09/19] Fixed a bug where channels could not specify workerId publication --- src/Traits/ChannelMethods.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index d3e7d16..5c7459e 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -95,7 +95,7 @@ protected static function _ChPublish(string $key, mixed $message, bool $store = if (!$channel) { if ($store) { // 非指定workerId - if ($workerId !== null) { + if ($workerId === null) { $channel['--default--']['value'][] = $message; } // 指定workerId @@ -108,7 +108,7 @@ protected static function _ChPublish(string $key, mixed $message, bool $store = // 否则将消息投入到每个worker的监听器数据中 else { // 非指定workerId - if ($workerId !== null) { + if ($workerId === null) { if ($store or isset($channel[$workerId]['futureId'])) { $channel[$workerId]['value'][] = $message; } From cbc351e7975453cb0841e35d3aae8e8781f9a339 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Fri, 17 May 2024 18:38:01 +0800 Subject: [PATCH 10/19] Fixed a bug where channels could not specify workerId publication --- src/Traits/ChannelMethods.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Traits/ChannelMethods.php b/src/Traits/ChannelMethods.php index 5c7459e..677e734 100644 --- a/src/Traits/ChannelMethods.php +++ b/src/Traits/ChannelMethods.php @@ -109,18 +109,18 @@ protected static function _ChPublish(string $key, mixed $message, bool $store = else { // 非指定workerId if ($workerId === null) { - if ($store or isset($channel[$workerId]['futureId'])) { - $channel[$workerId]['value'][] = $message; - } - } - // 指定workerId - else { foreach ($channel as $workerId => $item) { if ($store or isset($item['futureId'])) { $channel[$workerId]['value'][] = $message; } } } + // 指定workerId + else { + if ($store or isset($channel[$workerId]['futureId'])) { + $channel[$workerId]['value'][] = $message; + } + } } self::_Set($channelName, $channel); From 8d9e55cf601632379602fce6e4c5c4e96af1fca3 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Tue, 6 Aug 2024 10:46:39 +0800 Subject: [PATCH 11/19] update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 92e77b2..096a513 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,8 @@ } ); ``` + **Tips:Cache::Search()本质上是个扫表匹配的过程,是O(N)的操作,如果需要对特定族群的数据进行监听,推荐使用Channel相关函数实现监听。** + - **原子性执行** ```php From 88bdb40d4fd3127c7f2adfc19dfcd806ff97f9f9 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Tue, 6 Aug 2024 10:49:42 +0800 Subject: [PATCH 12/19] update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 096a513..e611fa5 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ Latest Stable Version - PHP Version Require + PHP Version Require - GitHub license + GitHub license
From e79d6b9a516c7e974c65c6b5696ed02470ece3ac Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Tue, 20 Aug 2024 12:43:44 +0800 Subject: [PATCH 13/19] update --- src/Commands/AbstractCommand.php | 34 +++++++++++++++++++ .../WorkbunnyWebmanSharedCacheClean.php | 6 ++-- .../WorkbunnyWebmanSharedCacheList.php | 7 ++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/Commands/AbstractCommand.php b/src/Commands/AbstractCommand.php index c9e2133..9da0291 100644 --- a/src/Commands/AbstractCommand.php +++ b/src/Commands/AbstractCommand.php @@ -7,18 +7,52 @@ abstract class AbstractCommand extends Command { + /** + * 兼容webman console,需重写 + * + * @var string + */ + protected static string $defaultName = ''; + /** + * 兼容webman console,需重写 + * + * @var string + */ + protected static string $defaultDescription = ''; + + /** + * 输出info + * + * @param OutputInterface $output + * @param string $message + * @return void + */ protected function info(OutputInterface $output, string $message): void { $output->writeln("ℹ️ $message"); } + /** + * 输出error + * + * @param OutputInterface $output + * @param string $message + * @return int + */ protected function error(OutputInterface $output, string $message): int { $output->writeln("❌ $message"); return self::FAILURE; } + /** + * 输出success + * + * @param OutputInterface $output + * @param string $message + * @return int + */ protected function success(OutputInterface $output, string $message): int { $output->writeln("✅ $message"); diff --git a/src/Commands/WorkbunnyWebmanSharedCacheClean.php b/src/Commands/WorkbunnyWebmanSharedCacheClean.php index ea5f1e3..a521b95 100644 --- a/src/Commands/WorkbunnyWebmanSharedCacheClean.php +++ b/src/Commands/WorkbunnyWebmanSharedCacheClean.php @@ -10,13 +10,15 @@ class WorkbunnyWebmanSharedCacheClean extends AbstractCommand { + protected static string $defaultName = 'workbunny:shared-cache-clean'; + protected static string $defaultDescription = 'Remove all workbunny/webman-shared-cache caches. '; + /** * @return void */ protected function configure(): void { - $this->setName('workbunny:shared-cache-clean') - ->setDescription('Remove all workbunny/webman-shared-cache caches. '); + $this->setName(static::$defaultName)->setDescription(static::$defaultDescription); } /** diff --git a/src/Commands/WorkbunnyWebmanSharedCacheList.php b/src/Commands/WorkbunnyWebmanSharedCacheList.php index 413e918..436b3f4 100644 --- a/src/Commands/WorkbunnyWebmanSharedCacheList.php +++ b/src/Commands/WorkbunnyWebmanSharedCacheList.php @@ -9,14 +9,15 @@ class WorkbunnyWebmanSharedCacheList extends AbstractCommand { + protected static string $defaultName = 'workbunny:shared-cache-list'; + protected static string $defaultDescription = 'Show workbunny/webman-shared-cache caches list. '; + /** * @return void */ protected function configure(): void { - $this->setName('workbunny:shared-cache-list') - ->setDescription('Show workbunny/webman-shared-cache caches list. '); - + $this->setName(static::$defaultName)->setDescription(static::$defaultDescription); $this->addOption('page', 'p', InputOption::VALUE_OPTIONAL, 'Page. ', 1); $this->addOption('size', 's', InputOption::VALUE_OPTIONAL, 'Page size. ', 20); } From d7965da3fd2db1989258ad7049c55554bc9e2fd6 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Tue, 20 Aug 2024 14:45:53 +0800 Subject: [PATCH 14/19] feat: hashKey add ttl --- src/Traits/HashMethods.php | 74 +++++++++++++++++++------- tests/HashTest.php | 104 ++++++++++++++++++++++++++++++------- 2 files changed, 142 insertions(+), 36 deletions(-) diff --git a/src/Traits/HashMethods.php b/src/Traits/HashMethods.php index ebed670..ec813a7 100644 --- a/src/Traits/HashMethods.php +++ b/src/Traits/HashMethods.php @@ -23,17 +23,22 @@ trait HashMethods * @param string $key * @param string|int $hashKey * @param mixed $hashValue + * @param int $ttl * @return bool */ - protected static function _HSet(string $key, string|int $hashKey, mixed $hashValue): bool + protected static function _HSet(string $key, string|int $hashKey, mixed $hashValue, int $ttl = 0): bool { $func = __FUNCTION__; $params = func_get_args(); self::_Atomic($key, function () use ( - $key, $hashKey, $hashValue, $func, $params + $key, $hashKey, $hashValue, $ttl, $func, $params ) { $hash = self::_Get($key, []); - $hash[$hashKey] = $hashValue; + $hash[$hashKey] = [ + '_value' => $hashValue, + '_ttl' => $ttl, + '_timestamp' => time() + ]; self::_Set($key, $hash); return [ 'timestamp' => microtime(true), @@ -51,19 +56,29 @@ protected static function _HSet(string $key, string|int $hashKey, mixed $hashVal * @param string $key * @param string|int $hashKey * @param int|float $hashValue + * @param int $ttl * @return bool|int|float */ - protected static function _HIncr(string $key, string|int $hashKey, int|float $hashValue = 1): bool|int|float + protected static function _HIncr(string $key, string|int $hashKey, int|float $hashValue = 1, int $ttl = 0): bool|int|float { $func = __FUNCTION__; $result = false; $params = func_get_args(); self::_Atomic($key, function () use ( - $key, $hashKey, $hashValue, $func, $params, &$result + $key, $hashKey, $hashValue, $ttl, $func, $params, &$result ) { $hash = self::_Get($key, []); - if (is_numeric($v = ($hash[$hashKey] ?? 0))) { - $hash[$hashKey] = $result = $v + $hashValue; + $value = $hash[$hashKey]['_value'] ?? 0; + $oldTtl = $hash[$hashKey]['_ttl'] ?? 0; + $timestamp = $hash[$hashKey]['_timestamp'] ?? 0; + if (is_numeric($value)) { + $now = time(); + $value = ($oldTtl <= 0 or (($timestamp + $oldTtl) >= $now)) ? $value : 0; + $hash[$hashKey] = [ + '_value' => $result = $value + $hashValue, + '_ttl' => ($ttl > 0) ? $ttl : ($timestamp > 0 ? $now - $timestamp : 0), + '_timestamp' => $now, + ]; self::_Set($key, $hash); } return [ @@ -82,19 +97,29 @@ protected static function _HIncr(string $key, string|int $hashKey, int|float $ha * @param string $key * @param string|int $hashKey * @param int|float $hashValue + * @param int $ttl * @return bool|int|float */ - protected static function _HDecr(string $key, string|int $hashKey, int|float $hashValue = 1): bool|int|float + protected static function _HDecr(string $key, string|int $hashKey, int|float $hashValue = 1, int $ttl = 0): bool|int|float { $func = __FUNCTION__; $result = false; $params = func_get_args(); self::_Atomic($key, function () use ( - $key, $hashKey, $hashValue, $func, $params, &$result + $key, $hashKey, $hashValue, $ttl, $func, $params, &$result ) { $hash = self::_Get($key, []); - if (is_numeric($v = ($hash[$hashKey] ?? 0))) { - $hash[$hashKey] = $result = $v - $hashValue; + $value = $hash[$hashKey]['_value'] ?? 0; + $oldTtl = $hash[$hashKey]['_ttl'] ?? 0; + $timestamp = $hash[$hashKey]['_timestamp'] ?? 0; + if (is_numeric($value)) { + $now = time(); + $value = ($oldTtl <= 0 or (($timestamp + $oldTtl) >= $now)) ? $value : 0; + $hash[$hashKey] = [ + '_value' => $result = $value - $hashValue, + '_ttl' => ($ttl > 0) ? $ttl : ($timestamp > 0 ? $now - $timestamp : 0), + '_timestamp' => $now, + ]; self::_Set($key, $hash); } return [ @@ -148,8 +173,12 @@ protected static function _HDel(string $key, string|int ...$hashKey): bool */ protected static function _HGet(string $key, string|int $hashKey, mixed $default = null): mixed { + $now = time(); $hash = self::_Get($key, []); - return $hash[$hashKey] ?? $default; + $value = $hash[$hashKey]['_value'] ?? $default; + $ttl = $hash[$hashKey]['_ttl'] ?? 0; + $timestamp = $hash[$hashKey]['_timestamp'] ?? 0; + return ($ttl <= 0 or (($timestamp + $ttl) >= $now)) ? $value : $default; } /** @@ -163,8 +192,11 @@ protected static function _HExists(string $key, string|int ...$hashKey): array { $hash = self::_Get($key, []); $result = []; + $now = time(); foreach ($hashKey as $hk) { - if (isset($hash[$hk])) { + $ttl = $hash[$hk]['_ttl'] ?? 0; + $timestamp = $hash[$hk]['_timestamp'] ?? 0; + if (($ttl <= 0 or (($timestamp + $ttl) >= $now)) and isset($hash[$hk]['_value'])) { $result[$hk] = true; } } @@ -181,11 +213,17 @@ protected static function _HExists(string $key, string|int ...$hashKey): array protected static function _HKeys(string $key, null|string $regex = null): array { $hash = self::_Get($key, []); - $keys = array_keys($hash); - if ($regex !== null) { - $keys = array_values(array_filter($keys, function($key) use ($regex) { - return preg_match($regex, $key); - })); + $keys = []; + $now = time(); + foreach ($hash as $hashKey => $hashValue) { + $ttl = $hashValue['_ttl'] ?? 0; + $timestamp = $hashValue['_timestamp'] ?? 0; + if (($ttl <= 0 or (($timestamp + $ttl) >= $now)) and isset($hashValue['_value'])) { + if ($regex !== null and preg_match($regex, $key)) { + continue; + } + $keys[] = $hashKey; + } } return $keys; } diff --git a/tests/HashTest.php b/tests/HashTest.php index d04126a..639fda8 100644 --- a/tests/HashTest.php +++ b/tests/HashTest.php @@ -13,7 +13,11 @@ public function testHashGet(): void // 单进程执行 $this->assertEquals(null, Cache::HGet($key, $hash)); apcu_add($key, [ - $hash => $hash + $hash => [ + '_value' => $hash, + '_ttl' => 0, + '_timestamp' => time() + ] ]); $this->assertEquals([], Cache::LockInfo()); $this->assertEquals($hash, Cache::HGet($key, $hash)); @@ -24,7 +28,11 @@ public function testHashGet(): void $this->assertEquals(null, Cache::HGet($key, $hash)); $this->childExec(static function (string $key, string $hash) { apcu_add($key, [ - $hash => $hash + $hash => [ + '_value' => $hash, + '_ttl' => 0, + '_timestamp' => time() + ] ]); }, $key, $hash); $this->assertEquals([], Cache::LockInfo()); @@ -42,7 +50,11 @@ public function testHashSet(): void $this->assertTrue(Cache::HSet($key, $hash, $hash)); $this->assertEquals([], Cache::LockInfo()); $this->assertEquals([ - $hash => $hash + $hash => [ + '_value' => $hash, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); // 清理 apcu_delete($key); @@ -54,7 +66,11 @@ public function testHashSet(): void }, $key, $hash); $this->assertEquals([], Cache::LockInfo()); $this->assertEquals([ - $hash => $hash + $hash => [ + '_value' => $hash, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); // 清理 apcu_delete($key); @@ -98,8 +114,12 @@ public function testHashExists(): void $this->assertEquals([], Cache::HExists($key, 'a')); apcu_add($key, [ - 'a' => 1, - 'b' => 2 + 'a' => [ + '_value' => 1 + ], + 'b' => [ + '_value' => 2 + ] ]); $this->assertEquals([ 'a' => true, 'b' => true @@ -116,15 +136,27 @@ public function testHashIncr(): void $this->assertFalse(apcu_fetch($key)); $this->assertEquals(1, Cache::HIncr($key, 'a')); $this->assertEquals([ - 'a' => 1 + 'a' => [ + '_value' => 1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->assertEquals(3, Cache::HIncr($key, 'a', 2)); $this->assertEquals([ - 'a' => 3 + 'a' => [ + '_value' => 3, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->assertEquals(4.1, Cache::HIncr($key, 'a', 1.1)); $this->assertEquals([ - 'a' => 4.1 + 'a' => [ + '_value' => 4.1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); // 清理 apcu_delete($key); @@ -135,19 +167,31 @@ public function testHashIncr(): void Cache::HIncr($key, 'a'); }, $key); $this->assertEquals([ - 'a' => 1 + 'a' => [ + '_value' => 1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->childExec(static function (string $key) { Cache::HIncr($key, 'a',2); }, $key); $this->assertEquals([ - 'a' => 3 + 'a' => [ + '_value' => 3, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->childExec(static function (string $key) { Cache::HIncr($key, 'a',1.1); }, $key); $this->assertEquals([ - 'a' => 4.1 + 'a' => [ + '_value' => 4.1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); // 清理 apcu_delete($key); @@ -160,15 +204,27 @@ public function testHashDecr(): void $this->assertFalse(apcu_fetch($key)); $this->assertEquals(-1, Cache::HDecr($key, 'a')); $this->assertEquals([ - 'a' => -1 + 'a' => [ + '_value' => -1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->assertEquals(-3, Cache::HDecr($key, 'a', 2)); $this->assertEquals([ - 'a' => -3 + 'a' => [ + '_value' => -3, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->assertEquals(-4.1, Cache::HDecr($key, 'a', 1.1)); $this->assertEquals([ - 'a' => -4.1 + 'a' => [ + '_value' => -4.1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); // 清理 apcu_delete($key); @@ -179,19 +235,31 @@ public function testHashDecr(): void Cache::HDecr($key, 'a'); }, $key); $this->assertEquals([ - 'a' => -1 + 'a' => [ + '_value' => -1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->childExec(static function (string $key) { Cache::HDecr($key, 'a', 2); }, $key); $this->assertEquals([ - 'a' => -3 + 'a' => [ + '_value' => -3, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); $this->childExec(static function (string $key) { Cache::HDecr($key, 'a', 1.1); }, $key); $this->assertEquals([ - 'a' => -4.1 + 'a' => [ + '_value' => -4.1, + '_ttl' => 0, + '_timestamp' => time() + ] ], apcu_fetch($key)); // 清理 apcu_delete($key); From ff34a7b3a0a1bcd6e31c7c52dd59d27aff16666b Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Tue, 20 Aug 2024 17:58:10 +0800 Subject: [PATCH 15/19] update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e611fa5..b65d495 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ - 支持 HSet/HGet/HDel/HKeys/HExists - 支持 HIncr/HDecr,支持浮点运算 - 支持 储存对象数据 + - 支持 HashKey的秒级过期时间【版本 ≥ 0.5】 - **通配符/正则匹配Search** ```php From 596dba974428818e8f8edcaa5c6484708a896337 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Tue, 20 Aug 2024 18:24:08 +0800 Subject: [PATCH 16/19] feat: Add HRecycle() to manually recycle expired hashKey --- src/Traits/HashMethods.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Traits/HashMethods.php b/src/Traits/HashMethods.php index ec813a7..92ea1e5 100644 --- a/src/Traits/HashMethods.php +++ b/src/Traits/HashMethods.php @@ -10,6 +10,7 @@ * @method static bool|int|float HIncr(string $key, string|int $hashKey, int|float $value = 1) Hash 自增 * @method static bool|int|float HDecr(string $key, string|int $hashKey, int|float $value = 1) Hash 自减 * @method static array HExists(string $key, string|int ...$hashKey) Hash key 判断 + * @method static void HRecycle(string $key) Hash key 过期回收 */ trait HashMethods { @@ -181,6 +182,38 @@ protected static function _HGet(string $key, string|int $hashKey, mixed $default return ($ttl <= 0 or (($timestamp + $ttl) >= $now)) ? $value : $default; } + /** + * 回收过期 hashKey + * + * @param string $key + * @return void + */ + protected static function _HRecycle(string $key): void + { + $func = __FUNCTION__; + $params = func_get_args(); + self::_Atomic($key, function () use ( + $key, $func, $params + ) { + $hash = self::_Get($key, []); + $now = time(); + foreach ($hash as $hashKey => $hashValue) { + $ttl = $hashValue['_ttl'] ?? 0; + $timestamp = $hashValue['_timestamp'] ?? 0; + if ($ttl > 0 and $timestamp > 0 and $timestamp + $ttl < $now) { + unset($hash[$hashKey]); + } + } + self::_Set($key, $hash); + return [ + 'timestamp' => microtime(true), + 'method' => $func, + 'params' => $params, + 'result' => null + ]; + }, true); + } + /** * hash key 判断 * From 273cf9c3eb0f9eb0a09f45be65c79adf84a3b29b Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Wed, 21 Aug 2024 15:30:53 +0800 Subject: [PATCH 17/19] feat: Add HRecycle() to manually recycle expired hashKey --- src/Cache.php | 2 +- .../WorkbunnyWebmanSharedCacheEnable.php | 72 +++++++++++++++++++ ....php => WorkbunnyWebmanSharedHRecycle.php} | 29 ++++++-- src/Traits/HashMethods.php | 20 ++++-- .../workbunny/webman-shared-cache/command.php | 17 +++++ 5 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 src/Commands/WorkbunnyWebmanSharedCacheEnable.php rename src/Commands/{WorkbunnyWebmanSharedCacheList.php => WorkbunnyWebmanSharedHRecycle.php} (52%) create mode 100644 src/config/plugin/workbunny/webman-shared-cache/command.php diff --git a/src/Cache.php b/src/Cache.php index 9a2d489..4d8ce9c 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -40,7 +40,7 @@ public static function __callStatic(string $name, array $arguments) throw new Error('PHP-ext apcu not enable. '); } if (!apcu_enabled()) { - throw new Error('You need run shared-cache-enable.sh. '); + throw new Error('You need run workbunny:shared-cache-enable/shared-cache-enable.sh command to enable APCu. '); } return call_user_func([self::class, "_$name"], ...$arguments); } diff --git a/src/Commands/WorkbunnyWebmanSharedCacheEnable.php b/src/Commands/WorkbunnyWebmanSharedCacheEnable.php new file mode 100644 index 0000000..1c81871 --- /dev/null +++ b/src/Commands/WorkbunnyWebmanSharedCacheEnable.php @@ -0,0 +1,72 @@ +setDescription('Enable APCu cache with specified settings.') + ->addOption('file', 'f', InputOption::VALUE_REQUIRED, 'Specify configuration name', 'apcu-cache.ini') + ->addOption('target', 't', InputOption::VALUE_REQUIRED, 'Specify target location', '/usr/local/etc/php/conf.d') + ->addOption('size', 'si', InputOption::VALUE_REQUIRED, 'Configure apcu.shm_size', '1024M') + ->addOption('segments', 'se', InputOption::VALUE_REQUIRED, 'Configure apcu.shm_segments', 1) + ->addOption('mmap', 'm', InputOption::VALUE_REQUIRED, 'Configure apcu.mmap_file_mask', '') + ->addOption('gc_ttl', 'gc', InputOption::VALUE_REQUIRED, 'Configure apcu.gc_ttl', 3600); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $fileName = $input->getOption('file'); + $target = $input->getOption('target'); + $shmSize = $input->getOption('size'); + $shmSegments = $input->getOption('segments'); + $mmapFileMask = $input->getOption('mmap'); + $gcTtl = $input->getOption('gc_ttl'); + + if (!is_dir($target)) { + return $this->error($output, "Target directory does not exist: $target. "); + } + $configContent = <<getHelper('question'); + $question = new ConfirmationQuestion("Configuration file already exists at $filePath. Overwrite? (y/N) ", false); + + if (!$helper->ask($input, $output, $question)) { + return $this->success($output, "Operation aborted. "); + } + } + + file_put_contents($filePath, $configContent); + return $this->success($output, "Configuration file created at: $filePath. "); + } + +} diff --git a/src/Commands/WorkbunnyWebmanSharedCacheList.php b/src/Commands/WorkbunnyWebmanSharedHRecycle.php similarity index 52% rename from src/Commands/WorkbunnyWebmanSharedCacheList.php rename to src/Commands/WorkbunnyWebmanSharedHRecycle.php index 436b3f4..3662cad 100644 --- a/src/Commands/WorkbunnyWebmanSharedCacheList.php +++ b/src/Commands/WorkbunnyWebmanSharedHRecycle.php @@ -2,15 +2,17 @@ namespace Workbunny\WebmanSharedCache\Commands; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Workbunny\WebmanSharedCache\Cache; -class WorkbunnyWebmanSharedCacheList extends AbstractCommand +class WorkbunnyWebmanSharedCacheHRecycle extends AbstractCommand { - protected static string $defaultName = 'workbunny:shared-cache-list'; - protected static string $defaultDescription = 'Show workbunny/webman-shared-cache caches list. '; + protected static string $defaultName = 'workbunny:shared-cache-hrecycle'; + protected static string $defaultDescription = 'Manually recycle expired hashKeys. '; /** * @return void @@ -18,8 +20,7 @@ class WorkbunnyWebmanSharedCacheList extends AbstractCommand protected function configure(): void { $this->setName(static::$defaultName)->setDescription(static::$defaultDescription); - $this->addOption('page', 'p', InputOption::VALUE_OPTIONAL, 'Page. ', 1); - $this->addOption('size', 's', InputOption::VALUE_OPTIONAL, 'Page size. ', 20); + $this->addOption('key', 'k', InputOption::VALUE_OPTIONAL, 'Cache Key. '); } /** @@ -29,8 +30,22 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { - $page = $input->getOption('page'); - $size = $input->getOption('size'); + $key = $input->getOption('key'); + if ($key) { + Cache::HRecycle($key); + } else { + $progressBar = new ProgressBar($output); + $progressBar->start(); + $keys = Cache::Keys(); + $progressBar->setMaxSteps(count($keys)); + foreach ($keys as $key) { + $value = Cache::Get($key); + if ( + ($value['_ttl'] ?? null) and ($value['_timestamp'] ?? null)) { + Cache::HRecycle($key); + } + } + } $headers = ['name', 'value']; $rows = []; // todo diff --git a/src/Traits/HashMethods.php b/src/Traits/HashMethods.php index 92ea1e5..aba6970 100644 --- a/src/Traits/HashMethods.php +++ b/src/Traits/HashMethods.php @@ -196,15 +196,21 @@ protected static function _HRecycle(string $key): void $key, $func, $params ) { $hash = self::_Get($key, []); - $now = time(); - foreach ($hash as $hashKey => $hashValue) { - $ttl = $hashValue['_ttl'] ?? 0; - $timestamp = $hashValue['_timestamp'] ?? 0; - if ($ttl > 0 and $timestamp > 0 and $timestamp + $ttl < $now) { - unset($hash[$hashKey]); + if (isset($hash['_ttl']) and isset($hash['_timestamp'])) { + $now = time(); + $set = false; + foreach ($hash as $hashKey => $hashValue) { + $ttl = $hashValue['_ttl'] ?? 0; + $timestamp = $hashValue['_timestamp'] ?? 0; + if ($ttl > 0 and $timestamp > 0 and $timestamp + $ttl < $now) { + $set = true; + unset($hash[$hashKey]); + } + } + if ($set) { + self::_Set($key, $hash); } } - self::_Set($key, $hash); return [ 'timestamp' => microtime(true), 'method' => $func, diff --git a/src/config/plugin/workbunny/webman-shared-cache/command.php b/src/config/plugin/workbunny/webman-shared-cache/command.php new file mode 100644 index 0000000..b872723 --- /dev/null +++ b/src/config/plugin/workbunny/webman-shared-cache/command.php @@ -0,0 +1,17 @@ + + * @copyright chaz6chez + * @link https://github.com/workbunny/webman-push-server + * @license https://github.com/workbunny/webman-push-server/blob/main/LICENSE + */ +declare(strict_types=1); + +return [ + Workbunny\WebmanSharedCache\Commands\WorkbunnyWebmanSharedCacheEnable::class, + Workbunny\WebmanSharedCache\Commands\WorkbunnyWebmanSharedCacheClean::class, +]; From a211048f1d0e7637d3fe68cd9aa5a4af233f7533 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Wed, 21 Aug 2024 15:32:30 +0800 Subject: [PATCH 18/19] feat: Add HRecycle() to manually recycle expired hashKey --- .../WorkbunnyWebmanSharedHRecycle.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Commands/WorkbunnyWebmanSharedHRecycle.php b/src/Commands/WorkbunnyWebmanSharedHRecycle.php index 3662cad..cdc8eaf 100644 --- a/src/Commands/WorkbunnyWebmanSharedHRecycle.php +++ b/src/Commands/WorkbunnyWebmanSharedHRecycle.php @@ -39,22 +39,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int $keys = Cache::Keys(); $progressBar->setMaxSteps(count($keys)); foreach ($keys as $key) { - $value = Cache::Get($key); - if ( - ($value['_ttl'] ?? null) and ($value['_timestamp'] ?? null)) { - Cache::HRecycle($key); - } + Cache::HRecycle($key); + $progressBar->advance(); } + $progressBar->finish(); } - $headers = ['name', 'value']; - $rows = []; - // todo - - $table = new Table($output); - $table->setHeaders($headers); - $table->setRows($rows); - $table->render(); - - return self::SUCCESS; + return $this->success($output, 'HRecycle Success. '); } } From de3d27fb3ead6b9f3db2cddcec8e267fe34472b1 Mon Sep 17 00:00:00 2001 From: chaz6chez Date: Thu, 22 Aug 2024 11:40:55 +0800 Subject: [PATCH 19/19] fix command bugs --- ...SharedHRecycle.php => WorkbunnyWebmanSharedCacheHRecycle.php} | 1 - src/config/plugin/workbunny/webman-shared-cache/command.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) rename src/Commands/{WorkbunnyWebmanSharedHRecycle.php => WorkbunnyWebmanSharedCacheHRecycle.php} (97%) diff --git a/src/Commands/WorkbunnyWebmanSharedHRecycle.php b/src/Commands/WorkbunnyWebmanSharedCacheHRecycle.php similarity index 97% rename from src/Commands/WorkbunnyWebmanSharedHRecycle.php rename to src/Commands/WorkbunnyWebmanSharedCacheHRecycle.php index cdc8eaf..11c72cd 100644 --- a/src/Commands/WorkbunnyWebmanSharedHRecycle.php +++ b/src/Commands/WorkbunnyWebmanSharedCacheHRecycle.php @@ -3,7 +3,6 @@ namespace Workbunny\WebmanSharedCache\Commands; use Symfony\Component\Console\Helper\ProgressBar; -use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; diff --git a/src/config/plugin/workbunny/webman-shared-cache/command.php b/src/config/plugin/workbunny/webman-shared-cache/command.php index b872723..aff331a 100644 --- a/src/config/plugin/workbunny/webman-shared-cache/command.php +++ b/src/config/plugin/workbunny/webman-shared-cache/command.php @@ -14,4 +14,5 @@ return [ Workbunny\WebmanSharedCache\Commands\WorkbunnyWebmanSharedCacheEnable::class, Workbunny\WebmanSharedCache\Commands\WorkbunnyWebmanSharedCacheClean::class, + Workbunny\WebmanSharedCache\Commands\WorkbunnyWebmanSharedCacheHRecycle::class ];