In Laravel 6, the cache is deleted for no reason (cache::tags)
1. The code for setting and obtaining the cache is as follows
const TAG_THEME_EDITOR = 'theme_editor'; // 缓存标签:主题编辑
const THEME_EDITOR_SESSION_PREFIX = 'theme_editor_session:'; // 缓存前缀:主题编辑会话
const TTL = 86400; // 缓存的过期时间(秒)
public static function saveSessionMetadata(string $sessionId, array $data): bool
{
return Cache::tags([
ThemePreviewInterface::TAG_THEME_EDITOR,
ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId]
)
->put(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId, json_encode($data), ThemePreviewInterface::TTL);
}
public static function getSessionMetadata(string $sessionId): array
{
$data = Cache::tags([
ThemePreviewInterface::TAG_THEME_EDITOR,
ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId
])
->get(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId);
if (empty($data)) {
return [];
}
return JsonDecoder::jsonToArray($data);
}
2. The expiration time (seconds) of the cache is 86400, but in the production environment, there is a very low probability that it will expire in less than 24 hours, and even the cache cannot be obtained in the past few hours. Print $DATA, its value is NULL.
3. Decide to eliminate the interference of external factors first, and start with the analysis of the program itself. It was initially suspected that the cache was operated somewhere in the program itself, resulting in the cache being cleared.
4. The cache related configuration in .env is as follows
CACHE_DRIVER=redis
REDIS_CLIENT=phpredis
5. Print the configuration item, database.reids.client=phpredis; cache.default=redis. as shown in Figure 1
/object # php artisan tinker
Psy Shell v0.9.12 (PHP 7.4.26 — cli) by Justin Hileman
>>> echo config('database.redis.client');
>>> echo config('database.redis.client');
phpredis⏎
>>> echo config('cache.default');
redis⏎
>>>
6. The configuration in /config/cache.php is as follows
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
],
],
7. The configuration in /config/database.php is as follows
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'predis'),
],
'cache' => [
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
'persistent'=>env('REDIS_PERSISTENT', true),
'persistent_id'=>env('REDIS_PERSISTENT_ID', 'cache'),
'retry_interval'=>env('REDIS_PERSISTENT_RETRY', 3000),
],
],
8. In the alias part of the /config/app.php configuration file, the alias of the appearance of Illuminate\Support\Facades\Redis is not renamed. However, it does not throw an exception: please remove the redis facade alias in your “app” configuration file in order to avoid collsion with the php redis extension.. Decided to ignore this issue for the time being.
'aliases' => [
'Redis' => Illuminate\Support\Facades\Redis::class,
],
9. Still decide to rename to other content, such as RedisManager, to avoid conflicts with the Redis class.
'aliases' => [
'RedisManager' => Illuminate\Support\Facades\Redis::class,
],
10. Decide to log in to Redis to confirm, first confirm the order of the execution command in the local environment. as shown in Figure 2
127.0.0.1:1>GET object_cache:tag:theme_editor_48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:key
"s:22:"631ae7ba62b83808649247";"
127.0.0.1:1>SMEMBERS object_cache:631ae7ba62b83808649247:standard_ref
1) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:9734e008-f539-4f76-9e9f-d8037334b968:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:product_detail"
2) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb"
127.0.0.1:1>GET object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb
"s:9329:"{"id":372}";"
127.0.0.1:1>
11. Decide to log in to Redis to confirm, and query based on Tinker. In the order of the above execution commands, to confirm whether there is a corresponding key in the local environment. as shown in Figure 3
PS E:\wwwroot\object> php artisan tinker
Psy Shell v0.9.12 (PHP 7.4.27 — cli) by Justin Hileman
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('object_cache:tag:theme_
editor_48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:key'));
string(30) "s:22:"631ae7ba62b83808649247";"
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->SMEMBERS('object_cache:631ae
7ba62b83808649247:standard_ref'));
array(2) {
[0]=>
string(186) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:9734e008-f539-4f76-9e9f-d8037334b968:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb:product_detail"
[1]=>
string(134) "object_cache:c613daaf6b394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb"
}
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->GET('object_cache:c613daaf6b
394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb'));
string(9339) "s:9329:"{"id":372}";"
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->TTL('object_cache:c613daaf6b
394e496382fd07bc808ea8a497556f:theme_editor_session:48wJgWKHnabdr3AznlDhP5onmEVedTz67ULb'));
int(81060)
=> null
>>>
12. TTL key, in seconds, return the remaining life time (TTL, time to live) of the given key. A key is generated at 2022/09/09 15:14. The current time is 2022/09/09 16:44 . 1 hour 30 minutes has passed. 86400 – 1 * 3600 – 30 * 60 = 81000. The remaining number of seconds to survive is: int (81060). in line with expectations. as shown in Figure 4
13. Decide to regenerate one online environment, and then check the number of seconds for the remaining survival time. The remaining number of survival seconds for a just generated key is: int (85730). in line with expectations. as shown in Figure 5
14. Decide to log in to Redis to confirm, and query based on Tinker. In the order of the above execution commands, to confirm whether there is a corresponding key in the online environment. Make sure the corresponding key is not completely emptied. If cache::tags(themepreviewInterface::tag_theme_editor is executed )._. $sessionId)->flush() or cache::flush(), then execute the query var_dump(\illuminate\support\facades\redis::connection(cache)->get(arrest9_cache:tag:theme_editor_uq6blxo7gsbwhqovcxqashx8sdas6bxh2lwe:key)); null will return null, but now there is a value. As shown in Figure 6, Figure 6.1
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('arrest9_cache:tag:theme_editor_Uq6BLXO7GsbWHQovCXQAshX8SdaS6BXh2lWE:key'));
string(30) "s:22:"63243db5c01db958165221";
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->TTL('arrest9_cache:tag:theme_editor_Uq6BLXO7GsbWHQovCXQAshX8SdaS6BXh2lWE:key'));
int(-1)
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->SMEMBERS('arrest9_cache:63243db5c01db958165221:standard_ref'));
array(0) {
}
=> null
>>>
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->get('object_cache:tag:theme_
editor_ERo5GoALUnDuxcMtW3yPYPKsjwci8DVZR2ij:key'));
NULL
=> null
>>> var_dump(\Illuminate\Support\Facades\Redis::connection('cache')->SMEMBERS('object_cache:632a7
fda56e94065934346:standard_ref'));
array(0) {
}
=> null
>>>
15. Decide to add corresponding logs based on event monitoring. Create a new listener file /app/listeners/cache/logKeyWrittenListener.php , when the key is written.
key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
Log::debug('theme_editor_session_key_written', [
'key' => $event->key,
'tags' => $event->tags,
'value' => $event->value,
'seconds' => $event->seconds
]);
}
}
}
16. The generated log information is as follows. as shown in Figure 7
[2022-09-15 17:26:05] local.DEBUG: theme_editor_session_key_written {
"key": "theme_editor_session:7JivgR1WqXVeE4hsyA335ILc60k153yTynIN",
"tags": [
"theme_editor",
"theme_editor_7JivgR1WqXVeE4hsyA335ILc60k153yTynIN"
],
"value": "",
"seconds": 86400
}
17. Create a new listener file /app/listeners/cache/logCacheHitListener.php , when the key is hit.
/**
* 处理事件
*
* @param CacheHit $event
* @return void
*/
public function handle(CacheHit $event)
{
if (config('app.debug') === true && strpos($event->key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
Log::debug('theme_editor_session_cache_hit', [
'key' => $event->key,
'tags' => $event->tags,
'value' => $event->value
]);
}
}
[2022-09-15 18:09:08] local.DEBUG: theme_editor_session_cache_hit {
"key": "theme_editor_session:KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D",
"tags": [
"theme_editor",
"theme_editor_KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D"
],
"value": ""
}
18. Create a new listener file /app/listeners/cache/logCacheMissedListener.php , when the key is missed.
/**
* 处理事件
*
* @param CacheMissed $event
* @return void
*/
public function handle(CacheMissed $event)
{
if (config('app.debug') === true && strpos($event->key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
Log::debug('theme_editor_session_cache_missed', [
'key' => $event->key,
'tags' => $event->tags
]);
}
}
[2022-09-15 18:10:15] local.DEBUG: theme_editor_session_cache_missed {
"key": "theme_editor_session:KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D",
"tags": [
"theme_editor",
"theme_editor_KrV0Nf5rMOiS5ZdF6i2uDandZZZWPPpXcQ4D"
]
}
19. However, when removing cached data with tags, use cache::tags(ThemepreviewInterface::tag_theme_editor ._. $SESSIONID)->flush(); , the listener /app/listeners/cache/logKeyForgottenListener.php does not generate logs, when clearing the key time.
/**
* 处理事件
*
* @param KeyForgotten $event
* @return void
*/
public function handle(KeyForgotten $event)
{
if (config('app.debug') === true && strpos($event->key, ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX) !== false) {
Log::debug('theme_editor_session_key_forgotten', [
'key' => $event->key,
'tags' => $event->tags
]);
}
}
20. Decided to use the flush method to clear all caches, cache::flush();. to confirm whether the event to clear the key will be triggered, and confirm that it has not been triggered. But when all cache operations are cleared, the cached data with tags can no longer be obtained. All cached data is emptied, regardless of whether it is labeled or not. as shown in Figure 8
21. So even if there are 4 cache log listeners, it is still difficult to judge when the key is cleared. You can only record the corresponding logs in the places that are expected to be cleared first.
22. For the 16th step, I decided to consult my operation and maintenance colleagues. Sometimes the operation and maintenance will run the python script for the dead key of Redis, and manually perform the cleaning operation. Therefore, it is decided to simulate and reproduce the same python script in the local environment. Execute: python batch-remove-rediskey.py /tmp. The locality has not been reproduced, and the analysis script may be reproduced online. However, the operation and maintenance has been determined, and this script has not been run manually during the recent problem period. as shown in Figure 9
PS E:\wwwroot\object> python batch-remove-rediskey.py /tmp
/tmp*
All done
23. Since 2 tags are set, the number of keys under one tag will continue to increase, and the number may eventually reach several thousand or even tens of thousands in a week. The final decision is to set only 1 tag. The code to set and get the cache is as follows
const TAG_THEME_EDITOR = 'theme_editor'; // 缓存标签:主题编辑
const THEME_EDITOR_SESSION_PREFIX = 'theme_editor_session:'; // 缓存前缀:主题编辑会话
const TTL = 86400; // 缓存的过期时间(秒)
public static function saveSessionMetadata(string $sessionId, array $data): bool
{
return Cache::tags([
ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId]
)
->put(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId, json_encode($data), ThemePreviewInterface::TTL);
}
public static function getSessionMetadata(string $sessionId): array
{
$data = Cache::tags([
ThemePreviewInterface::TAG_THEME_EDITOR . '_' . $sessionId
])
->get(ThemePreviewInterface::THEME_EDITOR_SESSION_PREFIX . $sessionId);
if (empty($data)) {
return [];
}
return JsonDecoder::jsonToArray($data);
}
24. After only 1 tag is set, and the number of keys in this tag is generally maintained at about 20, the cache cannot be hit, and the overall operation is normal. The preliminary analysis concluded that when using cache::tags, the number of tags under it should not be too much. It is recommended to set only 1, and the number of keys under each tag should not be too much. It is recommended not to exceed. 100.









